Sommaire

BONJOUR
Musique "Invention BWV784" J.S. BACH 1685-1750

Tutoriel et Help

API en C / ASM

Nous veillons aux respects des droits d'auteur : si par erreur un contenu protégé est disponible sans indication sur ce site, merci de contacter l'administrateur, il sera retiré ou mis à jour dans les plus brefs délais.


I. Les bases d'un programme Windows

Vu dans ... http://bob.developpez.com/tutapiwin/

I-A. Création d'une boîte de dialogue

I-A-1. Cours théorique

Bien comprendre le principe de fonctionnement d'une application Windows est essentiel avant de commencer à programmer. Je vais ici exposer le principe global de fonctionnement d'une application créant une fenêtre.

Tout d'abord, il convient de bien garder à l'esprit qu'un programme fonctionnant sous Windows et gérant une fenêtre doit rester en dialogue permanent avec Windows. Le programme ne connaît (a priori) rien sur son environnement. C'est Windows qui signale à l'application si elle doit redessiner le contenu d'une de ses fenêtres, ou encore si l'utilisateur essaie de fermer la fenêtre.

Cette communication se fait au travers de messages que Windows envoie à chaque fenêtre concernée. C'est au programme d'effectuer la réception de ces messages et de les transmettre aux fonctions gérant les différentes fenêtres. La réception de ces messages ne doit donc pas prendre de retard, et le traitement de chacun des messages doit être bref. En cas de retard, le redessinement des fenêtres n'est donc plus assuré, ce qui a pour conséquence des fenêtres blanches, non déplaçable, similaires à celles des programmes "plantés".

Chaque fenêtre est associée à une fonction ou procédure de fenêtre (Window Proc). Parfois plusieurs fenêtres peuvent être associées à une même procédure. Chaque message reçu est transmis à la procédure correspondante qui se chargera de traiter ce message (redessiner la fenêtre, la redimensionner, afficher un caractère entré par l'utilisateur…).

Une partie du travail de rafraîchissement de la fenêtre est pris en charge par Windows. Le programme n'a à redessiner que la zone client de sa fenêtre (et non pas la barre de titre, les menus éventuels…).

I-A-2. Projet N°1

Objectif : créer une boîte de dialogue et une procédure très simple chargée de la gérer.

Réalisation : la boîte de dialogue sera créée grâce à l'éditeur de ressources de VC++. Le programme sera chargé de la réception des messages et du traitement des plus simples.

Télécharger le projet ... href="http://bob.developpez.com/tutapiwin/projets/projet01.zip".

Le projet pas à pas :

Tout d'abord, il s'agit de créer le projet dans VC++ : File/New/Projects/Win32 Application. Donnez ensuite un nom à votre projet et cliquez 'Ok'. Ensuite, sélectionnez 'An empty project' puis 'Finish' et 'Ok'. Nous venons de créer un projet vide, il ne contient aucun fichier de code.

Ajoutons maintenant un fichier de code et fichier de ressources. Allez dans File/New/Files/C++ Source File. Appelez ce fichier main.cpp et validez. Refaites la même opération mais avec un fichier de type Ressource Script nommé res. Fermez le fichier qui s'est ouvert dans l'éditeur. Le script de ressources est associé à un fichier d'en-tête créé et mis à jour automatiquement, mais qu'il convient d'ajouter. Dans le feuillet FileView du panneau de gauche, faites un clic droit sur Header Files et sélectionnez Add Files to Folder. Ajoutez le fichier ressource.h qui doit être dans le même répertoire que votre projet.

Créons à présent le modèle de notre boîte de dialogue. Allez dans le feuillet RessourceView du panneau de travail (à gauche). Faites un clic droit sur res ressources et sélectionnez Insert/Dialog/New. Nous ne modifierons pas cette boîte de dialogue.

Occupons-nous maintenant du code qui va constituer le programme. Tout d'abord, les fichiers d'en-tête, deux sont nécessaires, Windows.h et ressource.h que nous avons inclus précédemment.

Le point d'entrée du programme est la fonction WinMain(). Elle est exécutée automatiquement par Windows lorsque le programme est démarré. Mais avant cette fonction, nous aurons la définition de la procédure de notre fenêtre principale, MainPoc().

#include <Windows.h>
#include "ressource.h"

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{

Nous allons maintenant créer la boîte de dialogue grâce à la fonction CreateDialog(). Le paramètre HINSTANCE est un identifiant de notre application, on passe l'identifiant fourni par Windows en paramètre de la fonction WinMain(). On donne ensuite l'identifiant ressource de notre boîte de dialogue. Elle n'a pas de fenêtre parent et la procédure est la fonction MainProc(). La fonction retourne une variable de type HWND qui identifie notre fenêtre. Puis, on affiche cette fenêtre. Toute fenêtre créée est invisible par défaut.

   HWND hDlg;
   hDlg=CreateDialog(hInstance,(LPCTSTR)IDD_DIALOG1,NULL,(DLGPROC)MainProc);
   ShowWindow(hDlg,SW_SHOW);

Occupons-nous maintenant de la réception des messages. La réception des messages est prise en charge par la fonction GetMessage(). Cette fonction retourne FALSE dès que le message WM_QUIT est reçu, ce qui indique que l'application doit être fermée.

   MSG msg;
   while(GetMessage(&msg,NULL,0,0)==TRUE)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return 0;
}

Il ne reste plus qu'à définir le comportement de notre fenêtre dans quelques cas simples: elle doit être fermée et l'application quittée si l'utilisateur presse 'ok' ou essaie de quitter. La fermeture de la fenêtre ne signifie pas forcément l'arrêt de l'application. Ces deux comportements correspondent à deux messages distincts: WM_QUIT indique que l'application doit se terminer, WM_CLOSE indique la fermeture de la fenêtre. Mais dans ce cas, on a une fenêtre un peu particulière, puisqu'il s'agit d'une boîte de dialogue. Sa procédure est définie de la même manière que celle d'une fenêtre.

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDOK:
         EndDialog(Dlg,0);
         PostQuitMessage(0);
         return TRUE;
      case IDCANCEL:
         EndDialog(Dlg,Select);
         PostQuitMessage(0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

Cette procédure est très simple, elle ne réagit qu'à la pression des boutons 'Ok' et 'Cancel' (le bouton ou la "croix"). En réponse à l'un ou l'autre de ces événements, la procédure demande la fermeture de la boîte de dialogue et termine l'application en envoyant un message WM_QUIT grâce à la fonction PostQuitMessage().

I-B. Création d'une fenêtre

I-B-1. Cours théorique

Dans la première partie de ce chapitre, nous avons vu comment créer une boite de dialogue. La création d'une boite de dialogue grâce à la fonction CreateDialog() est relativement simple puisqu'une grande partie du travail est effectué par CreateDialog().

Nous allons donc voir maintenant comment créer une fenêtre. Cette fenêtre pourra être personnalisée et ne se réferra à aucune ressource. Notre programme n'utilisera donc pas de ressources.

Comme nous avons pu le voir dans la partie précédente, le programme n'intervient en aucun cas dans le dessin de la fenêtre (barre de titre, menu...). Le programme n'est responsable que du redessinement de la zone client de la fenêtre. Comme il existe plusieurs types de fenêtre (avec un menu, sans menu, pouvant être minimisée ou pas...) il est nécessaire que de créer un 'protoype de fenêtre'. Ce prototype est aussi appelé classe de la fenêtre. C'est sur ce prototype que Windows se basera pour créer la fenêtre.

Une fois le prototype de fenêtre créé, nous demanderons à Windows de créer une fenêtre suivant ce prototype. La fenêtre créée devra elle aussi disposer d'une procédure permettant de traiter les messages qu'elle reçoit. Cette procédure est très similaire à celle gérant la boite de dialogue de la partie précédente. Elle ne traite cependant pas exactement les messages de la même manière.

I-B-2. Projet N°2

Objectif : créer une fenêtre sans utiliser de ressource et une procédure permettant sa gestion.

Réalisation : la fenêtre sera créée sans utiliser de ressource et sa procédure traitera les messages les plus simples.

Télécharger le projet ... http://bob.developpez.com/tutapiwin/projets/projet02.zip

Le projet pas à pas :

Il s'agit tout d'abord de créer un projet dans Microsoft Visual C++. Le projet sera créé de la même manière que dans la partie précédente, mais sans y ajouter de Ressource Script.

Le point d'entrée WinMain() est un standard à toute application Windows, ce programme débutera donc de la même manière que le précédent.

#include <Windows.h>

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{

Il faut maintenant créer une nouvelle classe de fenêtre. Cette classe est créée au moyen de la fonction RegisterClassEx(). La création de cette classe peut paraître fastidieuse et comprend un grand nombre de paramètres. nous n'utiliserons pas tous les paramètres, donc ne vous effrayez pas si vous ne comprenez pas totalement l'utilité de chacun d'eux.

   WNDCLASSEX principale;
   principale.cbSize=sizeof(WNDCLASSEX);
   principale.style=CS_HREDRAW|CS_VREDRAW;
   principale.lpfnWndProc=MainProc;
   principale.cbClsExtra=0;
   principale.cbWndExtra=0;
   principale.hInstance=hInstance;
   principale.hIcon=LoadIcon(NULL,IDI_APPLICATION);
   principale.hCursor=LoadCursor(NULL,IDC_ARROW);
   principale.hbrBackground=reinterpret_cast<HBRUSH>(COLOR_WINDOW+1);
   principale.lpszMenuName=NULL;
   principale.lpszClassName="std";
   principale.hIconSm=LoadIcon(NULL,IDI_APPLICATION);
   RegisterClassEx(&principale);

principale.style=CS_HREDRAW|CS_VREDRAW indique que notre fenêtre devra être redessinée en cas de redimensionnement horizontal ou vertical. Nous indiquons ensuite quelle procédure sera chargée de gérer la fenêtre. L'instance de notre application est elle aussi passée en paramètre. Les curseurs ou icônes de notre fenêtre sont ceux par défaut de Windows. La fenêtre n'as pas de menu, la couleur de fond est celle de Windows par défaut. Le nom de la classe de la fenêtre 'std' est un choix personnel. Il n'a d'importance que pour nous. Une fois la structure remplie, un appel à RegisterClassEx() demande à Windows de mémoriser la classe.

Maintenant que nous disposons de notre classe de fenêtre, il ne reste plus qu'a la créer grâce à la fonction CreateWindowEx().

   HWND hWnd;
   hWnd=CreateWindowEx(
      WS_EX_CLIENTEDGE,
      "std",
      "Notre fenêtre",
      WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      NULL,
      NULL,
      hInstance,
      NULL
   );

   ShowWindow(hWnd,SW_SHOW);

La réception des messages se fait exactement de la même manière que dans le projet précédent.

   MSG msg;
   while(GetMessage(&msg,NULL,0,0)==TRUE)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return 0;
}

La définition de la procédure de cette fenêtre est la même que dans le programme précédent. Mais son fonctionnement interne est différent.

Tout d'abord, cette fenêtre ne contient pas de bouton. Sa fermeture se fera lors d'un clic sur la 'croix'. Les messages non traités sont passés à DefWindowProc(). Les messages traités seront WM_PAINT et WM_DESTROY. WM_PAINT indique que nous devons redessiner la zone client. Ici le traitement de ce message ne fera rien et pourrait être ignoré. Il est inclus dans le seul but de comprendre le mécanisme de redessinement d'une fenêtre. Le message WM_DESTROY indique que la fenêtre est en train d'être détruite (probablement suite au clic sur la 'croix'). Le fermeture de la fenêtre ne signifie pas forcément l'arrêt de l'application. Mais comme dans ce cas notre application n'est formée que d'une fenêtre, nous demanderons à terminer l'application dans ce cas en postant un message WM_QUIT.

LRESULT CALLBACK MainProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam)
{
   HDC hDC;
   PAINTSTRUCT paintst;
   switch (mes)
   {
   case WM_PAINT:
      hDC=BeginPaint(hWnd,&paintst);
      EndPaint(hWnd,&paintst);
      return 0;
   case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
   default:
      return DefWindowProc(hWnd, mes, wParam, lParam);
   }
}

Le traitement du message WM_PAINT peut vous paraître étrange. En effet, pourquoi effectuer un BeginPaint() et un EndPaint() alors que nous ne dessinons rien. La raison est très simple. À partir du moment ou nous décidons de traiter ce message, nous devons assurer un traitement minimum par la suite de ces deux fonctions, même si rien n'est dessiné.

I-C. Affichage dans une fenêtre

I-C-1. Cours théorique

Maintenant que nous savons créer une fenêtre, nous allons voir comment dessiner dans sa zone client. L'affichage dans la zone client d'une fenêtre se fait au niveau du message WM_PAINT. Pour provoquer l'envoi d'un tel message, il faut demander le redessinement d'une zone de la fenêtre. Lors du dessin dans la partie WM_PAINT, il sera impossible d'afficher hors de la zone de redessinement.

Pour dessiner dans une fenêtre, nous allons utiliser un contexte d'affichage. Il est propre à la fenêtre dans laquelle nous dessinons et nous est fourni par Windows. Il identifie en fait la zone de l'écran dans laquelle nous allons dessiner.

I-C-2. Projet N°3

Objectif : créer une fenêtre et afficher l'heure.

Réalisation : la création de la fenêtre se fera de manière identique au projet précédent. Seule sera ajoutée la partie permettant l'affichage de l'heure courante.

Télécharger le projet ... href="http://bob.developpez.com/tutapiwin/projets/projet03.zip

Le projet pas à pas :

La création de la fenêtre se fait exactement de la même manière que dans le projet précédent. Nous allons simplement utiliser les fonctions contenues dans stdio.h.

#include <Windows.h>
#include <stdio.h>

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
   WNDCLASSEX principale;
   principale.cbSize=sizeof(WNDCLASSEX);
   principale.style=CS_HREDRAW|CS_VREDRAW;
   principale.lpfnWndProc=MainProc;
   principale.cbClsExtra=0;
   principale.cbWndExtra=0;
   principale.hInstance=hInstance;
   principale.hIcon=LoadIcon(NULL,IDI_APPLICATION);
   principale.hCursor=LoadCursor(NULL,IDC_ARROW);
   principale.hbrBackground=reinterpret_cast<HBRUSH>(COLOR_WINDOW+1);
   principale.lpszMenuName=NULL;
   principale.lpszClassName="std";
   principale.hIconSm=LoadIcon(NULL,IDI_APPLICATION);
   RegisterClassEx(&principale);

   HWND hWnd;
   hWnd=CreateWindowEx(
      WS_EX_CLIENTEDGE,
      "std",
      "Notre fenêtre",
      WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      NULL,
      NULL,
      hInstance,
      NULL
   );
   ShowWindow(hWnd,SW_SHOW);

Comme nous allons afficher l'heure, il est important que nous soyons prévenus de manière régulière. Nous allons demander à Windows de nous avertir toutes les secondes, de manière à afficher l'heure régulièrement. Notre fenêtre recevra un message WM_TIMER toutes les secondes.

SetTimer(hWnd,NULL,1000,NULL);

La réception des messages se fait exactement de la même manière que dans le projet précédent.

   MSG msg;
   while(GetMessage(&msg,NULL,0,0)==TRUE)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return 0;
}

La procédure fonctionne de manière identique que précédemment. Nous allons ajouter le traitement du message WM_TIMER qui sera reçu toutes les secondes. Ce message demandera le redessinement d'une zone de l'écran (la zone ou nous afficherons l'heure).

LRESULT CALLBACK MainProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam)
{
   HDC hDC;
   PAINTSTRUCT paintst;
   RECT rcClient;
   switch (mes)
   {
      case WM_TIMER:
         rcClient.top=0;
         rcClient.left=0;
         rcClient.right=100;
         rcClient.bottom=50;
         RedrawWindow(hWnd,&rcClient,NULL,RDW_ERASE|RDW_INVALIDATE|RDW_ERASENOW|RDW_NOCHILDREN);
         return 0;

Un message WM_PAINT sera donc reçu toutes les secondes et à chaque fois que la fenêtre doit être redessinée. La fenêtre doit être redessinée si elle est couverte puis découverte par une autre fenêtre (par exemple).

Nous allons afficher l'heure en utilisant une police spécifique. Une fois cette police créée, il est nécessaire d'indiquer que nous allons l'utiliser dans notre contexte d'affichage (HDC). Le contexte d'affichage (ou Device Context) réfère à la zone client de notre fenêtre et contient tous ses paramètres graphiques (disposition sur l'écran, nombre de couleurs affichables…). Il est identifié par la variable hDC qui nous est passée par Windows au travers de la fonction BeginPaint().

Une fois la police créée et le texte dessiné, il convient de détruire la police.

L'heure courante sera récupérée grâce à la fonction GetLocalTime().

   case WM_PAINT:
      char buf[256];
      SYSTEMTIME CurrentTime;
      HFONT hFont;
      hFont=CreateFont(20,0,0,0,700,FALSE,FALSE,FALSE,0,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH|FF_DONTCARE,"Comic Sans MS");
      hDC=BeginPaint(hWnd,&paintst);
      SelectObject(hDC,hFont);
      GetLocalTime(&CurrentTime);
      sprintf(buf,"%d : %d : %d",CurrentTime.wHour,CurrentTime.wMinute,CurrentTime.wSecond);
      TextOut(hDC,0,0,buf,strlen(buf));
      EndPaint(hWnd,&paintst);
      DeleteObject(hFont);
      return 0;
   case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
   default:
      return DefWindowProc(hWnd, mes, wParam, lParam);
   }
}

Il est essentiel de bien comprendre le principe d'affichage dans une fenêtre car il est très utilisé par Windows. Quel que soit le cas, l'affichage se fait toujours entre deux fonctions qui indiquent le début et la fin de l'affichage. Il ne faut jamais oublier de libérer le contexte d'affichage après l'avoir utilisé. Ici, la libération du contexte d'affichage se fait par la fonction EndPaint().

I-D. Lecture dans un fichier et boîte de dialogue

I-D-1. Cours théorique

Nous allons maintenant revenir sur un programme utilisant des ressources. En effet, l'utilisation de boites de dialogues créées en ressources permettent bien souvent de gagner un temps précieux pour des programmes de petites tailles et de faible complexité. C'est le cas du programme que nous allons réaliser ici.

Nous allons donc aborder deux thèmes principaux: les boites de dialogues et les contrôles prédéfinis, ainsi que la lecture d'un fichier par des fonctions de l'API Windows, donc sans utiliser les fonctions du C traditionnel. Les contrôles prédéfinis sont les 'edit boxes', 'combo boxes', 'buttons', etc. Leur utilisation est très simple, car ils sont gérés de manière entièrement autonome. Ils envoient à la fenêtre qui les a créés des messages de notification pour indiquer les actions de l'utilisateur : clic sur le bouton, saisie d'un caractère…

La lecture du fichier se fait de manière relativement simple, et fonctionne par un système de HANDLE (comme beaucoup de fonctions de l'API Windows) qu'il faut créer puis détruire.

I-D-2. Projet N°4

Objectif : créer une boite de dialogue permettant la saisie d'un fichier et affichant les 500 premiers octets de ce fichier.

Réalisation : nous allons ici utiliser une ressource pour créer la boite de dialogue. La lecture du fichier se fera au niveau de la procédure de la boite de dialogue, car elle est très brève. Nous utiliserons ici une fonction qui permet de créer la boite de dialogue et de relever les messages, la partie 'main' du programme sera donc très courte (2 lignes).

Télécharger le projet ... http://bob.developpez.com/tutapiwin/projets/projet04.zip

Le projet pas à pas :

Nous allons créer un programme avec un fichier de ressources, inclure les fichiers correspondants (voir parties précédentes). La fonction WinMain() est excessivement simple puisque la fonction DialogBox() s'occupe de créer la boîte de dialogue et de récupérer ses messages. Cette fonction retourne seulement une fois que la boite de dialogue est fermée.

#include <Windows.h>

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
   DialogBox(hInstance,(LPCTSTR)IDD_MAIN,NULL,(DLGPROC)MainProc);
   return 0;
}

La procédure de la boîte de dialogue est très simple, elle ne gère que trois messages provenant de trois boutons. IDOK est envoyé lors d'un clic sur le bouton OK, IDCANCEL est envoyé lors d'un clic sur la 'croix' et IDC_LIRE est un message envoyé lors d'un clic sur le bouton IDC_LIRE que nous avons créé. Les variables créées en début de procédure serviront à la lecture du fichier que nous verrons après.

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   char buf[501];
   HANDLE hFile;
   DWORD Read;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_LIRE:

La réelle nouveauté de ce programme va se faire ici. Il s'agit tout d'abord de récupérer le nom de fichier entré par l'utilisateur. Comme il est entré dans un contrôle prédéfini, nous allons utiliser la fonction GetDlgItemText() et nous placerons ce nom de fichier dans le buffer créé au début de cette procédure sous le nom de buf. Une fois ce nom de fichier récupéré, nous allons essayer d'ouvrir le fichier donné en lecture. Si nous n'y parvenons pas, un message d'erreur sera affiché grâce à la fonction MessageBox(). Le traitement du message sera alors arrêté.

Remarque: la fonction MessageBox() ne retourne qu'une fois que la boîte de dialogue créée est fermée. Mais la récupération des messages de notre boîte de dialogue est assurée par MessageBox(). Le traitement prolongé de ce message n'est donc pas gênant dans ce cas puisque les messages continueront d'arriver à notre procédure.

         GetDlgItemText(Dlg,IDC_FILENAME,buf,256);
         hFile=CreateFile(buf,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
         if(hFile==INVALID_HANDLE_VALUE)
         {
            MessageBox(Dlg,"Erreur, impossible d'ouvrir le fichier spécifié.","Erreur",MB_OK);
            return 0;
         }

Arrivés à ce stade nous savons que le fichier saisi est valide et qu'il a pu être ouvert. Il ne reste plus qu'à lire les 500 premiers octets. Ils seront placés dans le buffer. Comme cette chaîne vas être affichée, elle doit être terminée par un caractère NULL. C'est pour cette raison que buf a été défini comme un tableau de 501 caractères (500 pour la lecture et 1 pour le caractère NULL). Comme la lecture peut faire moins de 500 octets, nous nous baserons sur la valeur retournée par ReadFile() pour ajouter le caractère NULL. Puis nous afficherons ce buffer dans l'edit box créé a cet effet grâce à SetDlgItemText().

         ReadFile(hFile,buf,500,&Read,NULL);
         CloseHandle(hFile);
         buf[Read]='';
         SetDlgItemText(Dlg,IDC_TEXT,buf);
         return 0;

Le reste du traitement de la procédure ne présente pas de difficulté particulière. Il est similaire aux traitements précédents. La seule différence notable est lors de la fermeture de la boîte de dialogue. Comme elle a été créée avec la fonction DialogBox() il ne faut pas utiliser PostQuitMesage() pour la quitter mais EndDialog().

      case IDOK:
         EndDialog(Dlg,0);
         return TRUE;
      case IDCANCEL:
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

Ce programme est relativement simple. La gestion de l'interface graphique ne prend que quelques lignes. Nous voyons donc bien l'intérêt de l'utilisation des ressources et donc contrôles prédéfinis. Sans l'aide des ressources, ce programme aurait été beaucoup plus compliqué à réaliser.

I-E. Pas à pas pour C++ Builder

Nous allons reprendre la 4ème partie : "Lecture dans un fichier et boîte de dialogue." mais adaptée à C++ Builder. Deux procédures sont traitées dans ce document une pour BCB6 et une pour BCB4.

Pour les projets sans boîte de dialogue la procédure est la même sauf qu'il n'y a pas besoin de rajouter de fichiers ressources.

I-E-1. BCB 6

  • Faire "Nouveau". (appelle la boîte de dialogue Nouveaux éléments)
  • Sur la boîte de dialogue "Expert console" :
  • Sur "Type de source" sélectionner "C" ou "C++". (Pour l'exemple cela n'a pas d'importance)
  • Aucune des CheckBox à droite ne doit être cochée.
  • Cliquer sur le bouton "Ok".
  • Maintenant nous avons le code minimum pour ce type d'application :
#include <windows.h>
#pragma hdrstop

//---------------------------------------------------------------------------
#pragma argsused
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow)
{
        return 0;}
  • Enregister le projet sous le nom choisi. Ici "main.cpp" et "main.bpr". (Faire "Fichier" puis "Tout enregistrer")
  • J'ai utilisé "Resource WorkShop" (livré avec BCB) pour faire le fichier Dialog1.rc mais il peut se faire avec n'importe quel éditeur de ressource.
  • Puis il faut ajouter le fichier ressource au projet:
  • Sur le menu faire "Projet" puis "Ajouter au projet"
  • Selectionner le type de fichier voulu ici "Fichier ressource (*.rc)"
  • Puis sélectionner le fichier ici "Dialog1.rc"
  • Puis faire Ouvrir
  • Le code source est identique a l'exemple Visual C++ (J'ai mis les mêmes noms d'identificateur)
  • Les source des fichiers "Dialog1.rc" et "ressource.h" sont a la fin de ce document.

Fichier "main.cpp" :

#include <windows.h>
#pragma hdrstop //Pas obligatoire
#include "ressource.h"

//---------------------------------------------------------------------------

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

#pragma argsused //ajouté automatiquement mais pas nécéssaire.

WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, in
t nCmdShow)

{
     DialogBox(hInstance,(LPCTSTR)DIALOG_1,NULL,(DLGPROC)MainProc);
     return 0;
}
//---------------------------------------------------------------------------

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   char buf[501];
   HANDLE hFile;
   DWORD Read;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_LIRE:
         GetDlgItemText(Dlg,IDC_FILENAME,buf,256);
         hFile=CreateFile(buf,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
         if(hFile==INVALID_HANDLE_VALUE)
         {
            MessageBox(Dlg,"Erreur, impossible d'ouvrir le fichier spécifié.","Erreur",MB_OK);
            return 0;
         }
         ReadFile(hFile,buf,500,&Read,NULL);
         CloseHandle(hFile);
         buf[Read]='';
         SetDlgItemText(Dlg,IDC_TEXT,buf);
         return 0;

      case IDOK:
         EndDialog(Dlg,0);
         return TRUE;
      case IDCANCEL:
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

I-E-2. BCB 4

  • Faire "Nouveau". (appelle la boîte de dialogue Nouveaux éléments)
  • Sur la boîte de dialogue "Nouveaux éléments" Onglet "Nouveau" DbClk sur l'icône "Expert console". (appelle la boîte de dialogue "Expert d'application console")
  • Sur la boîte de dialogue "Expert d'application console" :
  • Sur "Type de fenêtre" sélectionner "Windows (GUI)".
  • Sur "Type d'exécution" sélectionner "EXE".
  • Clicker sur le bouton "Terminer".
  • Maintenant nous avons le code minimum pour ce type d'application :
#include <windows.h>
#pragma hdrstop
#include <condefs.h>

//---------------------------------------------------------------------------
#pragma argsused
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
        return 0;
}
  • Enregister le projet sous le nom choisi. Ici "main.cpp". (Faire "Fichier" puis "Tout enregistrer")
  • J'ai utilisé "Resource WorkShop" (livré avec BCB) pour faire le fichier Dialog1.rc mais il peut se faire avec n'importe quel éditeur de ressource.
  • Puis il faut ajouter le fichier ressource au projet:
  • Sur le menu faire "Projet" puis "Ajouter au projet"
  • Sélectionner le type de fichier voulu ici "Fichier ressource (*.rc)"
  • Puis sélectionner le fichier ici "Dialog1.rc"
  • Puis faire Ouvrir
  • Cela va ajouter cette ligne dans le code :
USERC("Dialog1.rc");
  • Le reste du code est identique a l'exemple Visual C++ (J'ai mis les mêmes noms d'identificateur)

Fichier "main.cpp" :

#include <windows.h>
#pragma hdrstop
#include <condefs.h>
#include "ressource.h"

//---------------------------------------------------------------------------
USERC("Dialog1.rc");
//---------------------------------------------------------------------------

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM );

#pragma argsused //ajouté automatiquement mais pas nécéssaire.
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
{
   DialogBox(hInstance,(LPCTSTR)DIALOG_1,NULL,(DLGPROC)MainProc);
   return 0;
}

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM )
{
   int Select;
   char buf[501];
   HANDLE hFile;
   DWORD Read;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_LIRE:
         GetDlgItemText(Dlg,IDC_FILENAME,buf,256);
         hFile=CreateFile(buf,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
         if(hFile==INVALID_HANDLE_VALUE)
         {
            MessageBox(Dlg,"Erreur, impossible d'ouvrir le fichier spécifié.","Erreur",MB_OK);
            return 0;
         }
         ReadFile(hFile,buf,500,&Read,NULL);
         CloseHandle(hFile);
         buf[Read]='';
         SetDlgItemText(Dlg,IDC_TEXT,buf);
         return 0;

      case IDOK:
         EndDialog(Dlg,0);
         return TRUE;
     case IDCANCEL:
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}
  • Autres fichiers
  • Identiques pour BCB4 et BCB6

Fichier "Dialog1.rc"

DIALOG_1 DIALOG 14, 30, 212, 227
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "DIALOG_1"
FONT 8, "MS Sans Serif"
{
 CONTROL "Ok", IDOK, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 56, 193, 36, 14
 CONTROL "Cancel", IDCANCEL, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 115, 193, 38, 14
 CONTROL "", IDC_FILENAME, "EDIT", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 26, 18, 91, 12
 CONTROL "", IDC_TEXT, "EDIT", ES_LEFT | ES_MULTILINE | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 18, 49, 175, 119
 CONTROL "Lire", IDC_LIRE, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 136, 18, 50, 14
}

Fichier "Ressource.h"

#define DIALOG_1    1

#define IDC_FILENAME 101
#define IDC_TEXT 102
#define IDC_LIRE 103

I-F. Lecture d'un fichier WAV

I-F-1. Cours théorique

A présent, nous allons réaliser un nouveau projet qui sera déjà un programme 'utile' en soi. Evidemment, il restera très sommaire et se contentera de lire un fichier WAV sélectionné par l'utilisateur.

Nous inviterons tout d'abord l'utilisateur à choisir le fichier qu'il veut lire à partir d'une boîte de dialogue 'parcourir'. Une fois que l'utilisateur aura choisi le fichier qu'il désire lire, il pourra choisir de l'écouter. La lecture du fichier WAV se fera grâce à une fonction multimédia fournie par Windows: sndPlaySound(). Cette fonction est entièrement autonome et lira le fichier en arrière plan sans la moindre intervention de la part du programme. Cet automatisme est très pratique puisqu'il permet de lire le fichier sans se soucier de rien. Cependant, les possibilités de cette fonction restent restreintes. On pourra se satisfaire de cette fonction dans quasiment toutes les applications multimédias.

I-F-2. Projet N°5

Objectif : créer une boite de dialogue permettant la saisie d'un fichier WAV puis sa lecture.

Réalisation : nous allons utiliser un fichier ressources qui sera ici très adapté. Le programme ne contiendra que la fonction WinMain() et une procédure pour la boite de dialogue.

Télécharger le projet ... http://bob.developpez.com/tutapiwin/projets/projet05.zip

Le projet pas à pas :

Comme dans le programme précédent, nous allons créer un projet d'application Win32, ainsi qu'un fichier de ressources. La fonction WinMain() contiendra simplement l'appel de notre boîte de dialogue. Remarquons tout de même que la fonction sndPlaySound() que nous allons utiliser se trouve dans la librairie winmm.lib qui n'est pas une librairie standard. Il faut donc ajouter cette librairie. Ceci peut être fait grâce au menu Project/Setting/Link/Input. Dans le champ contenant déjà les librairies par défaut, ajoutez winmm.lib. Sans cet ajout, nous obtiendrions une erreur à la compilation indiquant que la fonction ne peut pas être trouvée.

La boîte de dialogue contiendra un champ 'Edit Box' (IDC_NOMDEFICHIER) dans lequel sera affiché le nom du fichier à lire. Nous cocherons la case 'Read Only' dans les options de ce champ de manière à empêcher l'utilisateur de saisir lui-même un nom de fichier. De cette manière, la possibilité de choix erronés se trouve déjà limitée.

Nous ajouterons un bouton 'Parcourir' avec pour identifiant IDC_PARCOURIR et un bouton 'Lire' (IDC_LIRE). Pour quitter, l'utilisateur devra utiliser la 'croix' (ID_CANCEL).

La fonction WinMain() ne diffère pas par rapport au programme précédent (si ce n'est dans les entêtes à inclure). Nous ajouterons une variable globale de manière à pouvoir accéder à l'instance du programme dans toutes les fonctions du programme (et ici dans la procédure de la boîte de dialogue).

#include <Windows.h>
#include <mmsystem.h>
#include "ressource.h"

HINSTANCE hInst;

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
   hInst=hInstance;
   DialogBox(hInstance,(LPCTSTR)IDD_MAIN,NULL,(DLGPROC)MainProc);
   return 0;
}

La procédure de la boîte de dialogue n'est pas très compliquée. Il faudra toutefois y ajouter les boutons 'Lire' et 'Parcourir'. Le bouton parcourir sera le plus difficile à ajouter.

Nous allons réserver un buffer de taille MAX_PATH (qui est une constante marquant la taille maximale d'un nom de fichier), ainsi qu'une structure OPENFILENAME qui nous servira pour la boîte de dialogue 'Parcourir'.

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   char buf[MAX_PATH];
   OPENFILENAME DlgInfs;

   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_PARCOURIR:

La fonction qui affiche la boîte de dialogue 'Parcourir' est GetOpenfileName(). De manière à personnaliser cette boîte de dialogue à nos besoins, nous passons une structure contenant les caractéristiques de la boîte de dialogue. Comme nous n'utiliserons pas une grande partie des membres de cette structure, nous allons tous les mettre à 0 grâce à la fonction memset() du C standard. Ceci constitue une bonne habitude à prendre. Pour plus d'informations sur les paramètres et les possibilités de cette fonction référez-vous à l'aide de fournie par Microsoft.

         memset(&DlgInfs,0,sizeof(OPENFILENAME));

         DlgInfs.lStructSize=sizeof(OPENFILENAME);
         DlgInfs.hwndOwner=Dlg;
         DlgInfs.hInstance=hInst;
         DlgInfs.lpstrFilter="Fichiers WAV*.WAV";
         DlgInfs.lpstrFile=buf;
         DlgInfs.nMaxFile=MAX_PATH;
         DlgInfs.lpstrTitle="Choisissez le fichier à lire.";
         DlgInfs.Flags=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST;
         if(GetOpenFileName(&DlgInfs))
            SetDlgItemText(Dlg,IDC_NOMDEFICHIER,buf);
         return TRUE;
Lorsque l'utilisateur pressera le bouton 'Lire', il faudra récupérer le nom de fichier saisi dans le champ 'IDC_NOMDEFICHIER' et le lire grâce à la fonction sndPlaySound().
      case IDC_LIRE:
         GetDlgItemText(Dlg,IDC_NOMDEFICHIER,buf,MAX_PATH);

         sndPlaySound(buf,SND_ASYNC);
         return TRUE;
Il ne reste plus qu'à gérer la sortie de la boîte de dialogue. Dans le cas ou l'utilisateur quitte le programme, il faut stopper la lecture du son en cours. Pour cela on utilisera encore la fonction sndPlaySound() avant de quitter.
      case IDCANCEL:
         sndPlaySound(NULL,NULL);
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

Ce programme ne présente aucune difficulté particulière. Il se suffit à lui-même bien qu'étant particulièrement pauvre. Ceci permet de donner une idée de la gestion des boîtes de dialogues qui seront dans le futur intégrées à un programme bien plus important. Bien que la gestion de telles boîtes de dialogue constitue un détail dans un programme plus important, il est nécessaire de bien comprendre leur fonctionnement de manière à ne pas perdre de temps et pouvoir créer rapidement une interface graphique agréable.

I-G. Création d'une application console

I-G-1. Cours théorique

Avant de s'intéresser aux applications consoles, il convient de bien comprendre de quoi il s'agit. Une application console n'est pas un programme DOS. C'est un programme Windows utilisant les fonctions de l'API Windows mais qui s'affiche dans une console DOS au lieu d'une fenêtre classique. Un tel programme ne peut pas être démarré en mode DOS réel.

L'avantage de ces programmes est qu'il est inutile de se préoccuper de l'affichage de la fenêtre puisque toutes les interventions sur la fenêtre sont prises en compte par le système. Ce type d'application est très utile pour de petits utilitaires fonctionnant en ligne de commande (bien qu'ils paraissent austères !).

Cependant, ces programmes fonctionnent de la même manière que les applications DOS (pour ce qui est de l'entrée sortie sur l'écran). On pourra donc utiliser les fonctions printf() et scanf() du C standard pour afficher des données à l'écran. De même les fonctions de flux standard C++ cin et cout sont utilisables.

Cette partie ne fera pas l'objet d'un projet, mais les applications console seront utilisées plus tard pour illustrer des programmes simples et ne nécessitant pas ou peu de communications avec l'utilisateur, de manière à réduire la taille des programmes.

I-H. Fenêtre classique ou ressource ?

I-H-1. Cours théorique

Maintenant que nous avons créé et utilisé ces deux types de fenêtres, il convient de s'interroger sur leur utilisation. Faut-il préférer les ressources ou plutôt les fenêtres classiques ? Quels sont les avantages des une et des autres ?

Les fenêtres créées à partir de ressources (souvent les boîtes de dialogue) sont évidemment bien plus rapides à créer. De plus leur utilisation nécessite beaucoup moins de code. Dans ce cas, pourquoi continuer à utiliser des fenêtres classiques créées avec l'API Windows ?

Si les ressources peuvent séduire, il faut cependant s'en méfier. Elles conviennent très bien pour des boîtes de dialogue invitant l'utilisateur à saisir des valeurs, ou à définir certains paramètres. C'est d'ailleurs leur rôle principal. En les utilisant dans ce contexte on gagne un temps très important par rapport à des fenêtres créées “manuellement”. Cependant, l'affichage dans une boîte de dialogue ne se prête que peu à la personnalisation.

Pour afficher des données comme des images, des polices personnalisées ou autre, il reste préférable d'utiliser une fenêtre classique. De même si la fenêtre doit gérer des interventions utilisateur comme une touche entrée, un clic, il faut alors passer à une fenêtre classique. Dans ces cas, la gestion automatisée devient en fait un obstacle à l'interception de ces événements.

Avant de se lancer dans la programmation d'une fenêtre classique ou au contraire de fenêtres ressources, il faut donc réfléchir à l'utilisation qui sera faite de ces fenêtres. Ni l'une ni l'autre de ces méthodes ne sont à bannir. Au contraire, il faut connaître les deux et savoir utiliser l'une ou l'autre dès qu'il le faut. La facilité des ressources ne doit pas en faire un réflexe systématique.

Bien entendu on ne peut pas établir des caractéristiques types pour lesquelles il faut absolument utiliser tel ou tel type de fenêtre. Cette décision doit prendre en compte les exigences du programme... Je ne me risquerais pas à dresser une liste exhaustive de tous les cas ou l'utilisation de telle ou telle méthode est préférable mais j'invite seulement le programmeur à réfléchir avant d'utiliser les ressources.


API Microsoft

Windows API ou WinAPI est le nom donné par Microsoft à l'Interface de programmation (API) sur les dispositifs d'exploitation Microsoft Windows. Elle est conçue pour les langages de programmation C et C++ et est la manière la plus directe pour une application d'interagir avec le dispositif d'exploitation Windows.

Un kit de développement (SDK) est disponible pour Windows qui apporte la documentation et les outils pour permettre aux developpeurs de créér des logiciels en utilisant cette API et les technologies associées.

Composant de Windows API

Les fonctionnalités apportées par Windows API peuvent être rangées dans sept catégories :

Services de base

Donne accès aux ressources principales du dispositif disponibles sous Windows. Exemples : Dispositif de fichiers, périphériques, processus, processus léger, accès au registre dispositif et au Dispositif de gestion d'exceptions. Ces fonctions sont stockées dans les fichiers kernel. exe, krnl286. exe ou krnl386. exe pour les dispositifs 16-bits et kernel32. dll et advapi32. dll sur les dispositifs 32 bits.

Interface graphique

Permet d'accéder aux ressources pour l'affichage sur les moniteurs, imprimantes, ... Elle est stockée dans le fichier gdi. exe sur les dispositifs 16-bits et gdi32. dll sur les dispositifs 32-bits.

Interface utilisateur

Permet d'afficher et de gérer les contrôles de base comme les boutons et barres de défilement, de recevoir les informations du clavier et de la souris et des fonctionnalités associées comme l'Environnement graphique. Ces fonctionnalités sont stockées dans le fichier user. exe sur les dispositifs 16-bits et user32. dll sur les dispositifs 32-bits. Depuis Windows XP, le fichier a été renommé comctl32. dll, elle a été regroupée avec les commandes communes.

Contrôle des boîtes de dialogue

Permet d'afficher les Boîtes de dialogue pour ouvrir et enregistrer des fichiers, choisir la couleur et la police, ... Elle réside dans le fichier comdlg. dll sur les dispositifs 16-bits et comdlg32. dll sur les dispositifs 32-bits. Elle a été groupée avec la partie Interface utilisateur.

Bibliothèques communes de commande

Donne accès à des fonctions avancées du dispositif d'exploitation comme des barres de statut (situées au bas des fenêtres), barres de progression, barres d'outils et onglets. Ces commandes résident dans la bibliothèque logicielle commctrl. dll sur les dispositifs 16-bits et comctl32. dll. Elle est groupée avec l'interface utilisateur.

Shell Windows

Le composant de Windows API autorise des applications d'accéder aux fonctionnalités apportées par le shell du dispositif d'exploitation. Elle réside dans le fichier shell. dll sur les dispositifs 16-bits et shell32. dll sur les dispositifs 32-bits, et depuis Windows 95 dans le fichier shlwapi. dll. Elle a été groupée avec la partie Interface utilisateur.

Services réseau

Donne accès aux diverses possibilités de gestion de réseau du dispositif d'exploitation. Ses sous-composants incluent NetBIOS, Winsock, RPC... Sur les anciennes versions de Windows, cela incluait NetDDE.

Autres API liées

Internet explorer offre quelques API qui sont fréquemment utilisées par les programmes et peut par conséquent être reconnu comme une partie de Windows API. Internet explorer est apporté d'office depuis Windows 98. Il est prévu d'arrêter cette prise en charge avec Vista.

Depuis Windows 95, il y aussi des API pour les affichages graphiques qui permettent d'avoir accès aux ressources de DirectX.

Il y a également d'autres API pour les relations entre les applications comme DDE (Dynamic Data Exchange, Echange dynamique de données) , OLE (Object Linking and Embedding) (voir (en) Object Linking and Embedding) et COM (Component Object Model) .

Versions

Chaque version de Windows a apporté son lot de modifications de l'API. Malgré cela, le nom de l'API reste le même sauf pour les modification majeures. Microsoft a lorsque même changé le nom Win32 en Windows API pour les contenir dans une seule famille même pour une prochaine modification de celle-ci.

  • Win16 était la première API pour les versions 16-bits du dispositif. Elle était au départ simplement appelé Windows API mais a été renommé en Win16 pour permettre le passage au 32-bits de Windows API. Les fonctionnalités étaient essentiellement dans le noyau du dispositif : kernel. exe (ou krnl286. exe ou krnl386. exe), user. exe et gdi. exe. Malgré l'extension exe, ces fichiers sont des bibliothèques de liens dynamiques.
  • Win32 est la version 32-bits de l'API pour les dispositifs plus récents. L'api se compose d'implémentations, comme avec Win16, dans le dispositif bibliothèques de liens dynamiques. Le noyau de Win32 réside dans les fichiers kernel32. dll, user32. dll, et gdi32. dll. Win32 a été présenté avec Windows NT. La version embarquée avec Windows 95 était nommée Win32c, avec "c" pour compatibilité (compatibility en anglais) mais ce nom a été abandonné pour Win32. Dans la version Windows NT 4.0 et suivantes, les appels de Win32 sont exécutés par deux modules, csrss. exe (Client/Server Runtime Server Subsystem) en mode utilisateur et win32k. sys en mode noyau.
  • Win32s est une extension de Win32 pour les dispositifs Windows 3. x qui a été introduite comme sous-ensemble de Win32. Le "s" est pour sous-ensemble (subset en anglais).
  • Win32 pour les éditions 64bits auparavant nommée Win64 est la version pour les ordinateurs 64-bits, avec les versions Windows XP Professional x64 Edition pour les processeurs x86-64 mais aussi Windows XP 64-bit Edition et Windows Server 2003 pour les processeurs Itanium. Cette version apporte juste le support pour ces deux nouvelles plateformes.

Autres implémentations

Bien que Windows API soit soumise aux droits d'auteurs et malgré les précédents légaux aux États-Unis, il est permis d'émuler cette API sur d'autres plateformes.

C'est le cas par exemple de WINE qui émule une API compatible avec Win32 pour les dispositifs d'exploitation à base UNIX. Un autre exemple est le dispositif ReactOS.

Liens externes

Windows API Reference sur MSDN : la référence à la source.

Spy Studio Surveiller l'activité.

news ://fr. comp. os. ms-windows. programmation Newsgroup français dédié à la programmation des API Win32

http ://www. charlespetzold. com/pw5/ Livre de référence indispensable pour acquérir les bases


Tutorial: A Simple Window

theForger's Win32 API Programming Tutorial

Home

Basics
  1. Getting Started
  2. A Simple Window
  3. Handling Messages
  4. The Message Loop
  5. Using Resources
  6. Menus and Icons
  7. Dialog Boxes
  8. Modeless Dialogs
  9. Standard Controls
  10. Dialog FAQ
Creating a simple application
  1. Creating controls at runtime
  2. Files and the common dialogs
  3. Tool and Status bars
  4. Multiple Document Interface
Graphics Device Interface
  1. Bitmaps and Device Contexts
  2. Transparency
  3. Timers and Animation
  4. Text, Fonts and Colours
Tools and Documentation
  1. References
  2. Free Visual C++
Appendices
  1. Solutions to Common Errors
  2. API vs. MFC
  3. Resource file notes

A Simple Window

Vu dans ... http://www.winprog.org/tutorial/simple_window.html

Example: simple_window

Sometimes people come on IRC and ask "How do I make a window?"...Well it's not entirely that simple I'm afraid. It's not difficult once you know what you're doing but there are quite a few things you need to do to get a window to show up; And they're more than can be simply explained over a chat room, or a quick note.

I always liked to do things first and learn them later...so here is the code to a simple window which will be explained shortly.

#include <windows.h>

            const char g_szClassName[] = "myWindowClass";

// Step 4: the Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    //Step 1: Registering the Window Class
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // Step 2: Creating the Window
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // Step 3: The Message Loop
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}
For most part this is the simplest windows program you can write that actually creates a functional window, a mere 70 or so lines. If you got the first example to compile then this one should work with no problems.

Step 1: Registering the Window Class

A Window Class stores information about a type of window, including it's Window Procedure which controls the window, the small and large icons for the window, and the background color. This way, you can register a class once, and create as many windows as you want from it, without having to specify all those attributes over and over. Most of the attributes you set in the window class can be changed on a per-window basis if desired.

A Window Class has NOTHING to do with C++ classes.

const char g_szClassName[] = "myWindowClass";
The variable above stores the name of our window class, we will use it shortly to register our window class with the system.
    WNDCLASSEX wc;

    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

This is the code we use in WinMain() to register our window class. We fill out the members of a WNDCLASSEX structure and call RegisterClassEx().

The members of the struct affect the window class as follows:

cbSize
The size of the structure.
style
Class Styles (CS_*), not to be confused with Window Styles (WS_*) This can usually be set to 0.
lpfnWndProc
Pointer to the window procedure for this window class.
cbClsExtra
Amount of extra data allocated for this class in memory. Usually 0.
cbWndExtra
Amount of extra data allocated in memory per window of this type. Usually 0.
hInstance
Handle to application instance (that we got in the first parameter of WinMain()).
hIcon
Large (usually 32x32) icon shown when the user presses Alt+Tab.
hCursor
Cursor that will be displayed over our window.
hbrBackground
Background Brush to set the color of our window.
lpszMenuName
Name of a menu resource to use for the windows with this class.
lpszClassName
Name to identify the class with.
hIconSm
Small (usually 16x16) icon to show in the taskbar and in the top left corner of the window.
Don't worry if that doesn't make much sense to you yet, the various parts that count will be explained more later. Another thing to remember is to not try and remember this stuff. I rarely (never) memorize structs, or function parameters, this is a waste of effort and, more importantly, time. If you know the functions you need to call then it is a matter of seconds to look up the exact parameters in your help files. If you don't have help files, get them. You are lost without. Eventually you will come to know the parameters to the functions you use most.

We then call RegisterClassEx() and check for failure, if it fails we pop up a message which says so and abort the program by returning from the WinMain() function.

Step 2: Creating the Window

Once the class is registered, we can create a window with it. You should look up the paramters for CreateWindowEx() (as you should ALWAYS do when using a new API call), but I'll explain them briefly here.

    HWND hwnd;

    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

The first parameter (WS_EX_CLIENTEDGE) is the extended windows style, in this case I have set it to give it a sunken inner border around the window. Set it to 0 if you'd like to see the difference. Also play with other values to see what they do.

Next we have the class name (g_szClassName), this tells the system what kind of window to create. Since we want to create a window from the class we just registered, we use the name of that class. After that we specify our window name or title which is the text that will be displayed in the Caption, or Title Bar on our window.

The parameter we have as WS_OVERLAPPEDWINDOW is the Window Style parameter. There are quite a few of these and you should look them up and experiment to find out what they do. These will be covered more later.

The next four parameters (CW_USEDEFAULT, CW_USEDEFAULT, 320, 240) are the X and Y co-ordinates for the top left corner of your window, and the width and height of the window. I've set the X and Y values to CW_USEDEFAULT to let windows choose where on the screen to put the window. Remeber that the left of the screen is an X value of zero and it increases to the right; The top of the screen is a Y value of zero which increases towards the bottom. The units are pixels, which is the smallest unit a screen can display at a given resolution.

Next (NULL, NULL, g_hInst, NULL) we have the Parent Window handle, the menu handle, the application instance handle, and a pointer to window creation data. In windows, the windows on your screen are arranged in a heirarchy of parent and child windows. When you see a button on a window, the button is the Child and it is contained within the window that is it's Parent. In this example, the parent handle is NULL because we have no parent, this is our main or Top Level window. The menu is NULL for now since we don't have one yet. The instance handle is set to the value that is passed in as the first parameter to WinMain(). The creation data (which I almost never use) that can be used to send additional data to the window that is being created is also NULL.

If you're wondering what this magic NULL is, it's simply defined as 0 (zero). Actually, in C it's defined as ((void*)0), since it's intended for use with pointers. Therefore you will possibly get warnings if you use NULL for integer values, depending on your compiler and the warning level settings. You can choose to ignore the warnings, or just use 0 instead.

Number one cause of people not knowing what the heck is wrong with their programs is probably that they didn't check the return values of their calls to see if they failed or not. CreateWindow() will fail at some point even if you're an experianced coder, simply because there are lots of mistakes that are easy to make. Untill you learn how to quickly identify those mistakes, at least give yourself the chance of figuring out where things go wrong, and Always check return values!

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

After we've created the window and checked to make sure we have a valid handle we show the window, using the last parameter in WinMain() and then update it to ensure that it has properly redrawn itself on the screen.

   ShowWindow(hwnd, nCmdShow);
   UpdateWindow(hwnd);

The nCmdShow parameter is optional, you could simply pass in SW_SHOWNORMAL all the time and be done with it. However using the parameter passed into WinMain() gives whoever is running your program to specify whether or not they want your window to start off visible, maximized, minimized, etc... You will find options for these in the properties of windows shortcuts, and this parameter is how the choice is carried out.

Step 3: The Message Loop

This is the heart of the whole program, pretty much everything that your program does passes through this point of control.

    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;

GetMessage() gets a message from your application's message queue. Any time the user moves the mouse, types on the keyboard, clicks on your window's menu, or does any number of other things, messages are generated by the system and entered into your program's message queue. By calling GetMessage() you are requesting the next available message to be removed from the queue and returned to you for processing. If there is no message, GetMessage() Blocks. If you are unfamiliar with the term, it means that it waits untill there is a message, and then returns it to you.

TranslateMessage() does some additional processing on keyboard events like generating WM_CHAR messages to go along with WM_KEYDOWN messages. Finally DispatchMessage() sends the message out to the window that the message was sent to. This could be our main window or it could be another one, or a control, and in some cases a window that was created behind the scenes by the sytem or another program. This isn't something you need to worry about because all we are concerned with is that we get the message and send it out, the system takes care of the rest making sure it gets to the proper window.

Step 4: the Window Procedure

If the message loop is the heart of the program, the window procedure is the brain. This is where all the messages that are sent to our window get processed.
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

The window procedure is called for each message, the HWND parameter is the handle of your window, the one that the message applies to. This is important since you might have two or more windows of the same class and they will use the same window procedure (WndProc()). The difference is that the parameter hwnd will be different depending on which window it is. For example when we get the WM_CLOSE message we destroy the window. Since we use the window handle that we received as the first paramter, any other windows will not be affected, only the one that the message was intended for.

WM_CLOSE is sent when the user presses the Close Button [x] or types Alt-F4. This will cause the window to be destroyed by default, but I like to handle it explicitly, since this is the perfect spot to do cleanup checks, or ask the user to save files etc. before exiting the program.

When we call DestroyWindow() the system sends the WM_DESTROY message to the window getting destroyed, in this case it's our window, and then destroys any remaining child windows before finally removing our window from the system. Since this is the only window in our program, we are all done and we want the program to exit, so we call PostQuitMessage(). This posts the WM_QUIT message to the message loop. We never receive this message, because it causes GetMessage() to return FALSE, and as you'll see in our message loop code, when that happens we stop processing messages and return the final result code, the wParam of WM_QUIT which happens to be the value we passed into PostQuitMessage(). The return value is only really useful if your program is designed to be called by another program and you want to return a specific value.

Step 5: There is no Step 5

Phew. Well that's it! If I haven't explained stuff clearly enough yet, just hang in there and hopefully things will become more clear as we get into more usefull programs.

Copyright © 1998-2013, Brook Miles @winprog.org). All rights reserved.


API Windows en C

Les ressources

Table des matières Plier Déplier

  • I. Introduction
  • II. Incorporation des ressources à l'exécutable
  • III. Le script de ressources
  • IV. Ressources de type menu
  • V. Code complet
    • V-A. resource.h
    • V-B. resource.rc
    • V-C. winmain.c

Les ressources. ♪

Article lu 6039 fois.

L'auteur

CGi Profil Pro Site personnel

L'article

Publié le 15 juin 2005 - Mis à jour le 4 janvier 2013

I. Introduction

Dans ce chapitre nous allons aborder les ressources.

Tout d'abord qu'appelle-t-on les ressources dans un programme Windows ?

Ce sont des données de toutes natures qui sont incorporées à l'exécutable. Cela peut être des images, des icônes, des chaînes de caractères, des blocs de données de toutes sortes… Mais aussi des boîtes de dialogue et des menus qui seront construits à partir de script.

Image non disponible

II. Incorporation des ressources à l'exécutable

L'incorporation des ressources à l'exécutable est réalisée par le linker. Nous devons lui fournir un fichier contenant les ressources. Ces fichiers ont l'extension « .res ». Ils sont compilés par un compilateur de ressources, auquel on fournit un fichier de script de ressources. Les fichiers de script de ressources sont des fichiers texte ayant l'extension « .rc ». Il existe aussi des éditeurs de ressources permettant de créer visuellement les fichiers ressources.

Ce tutoriel n'étant pas destiné à des outils particuliers, nous utiliserons uniquement les scripts de ressources dans les exemples qui vont suivre.

Si vous utilisez un EDI, il suffit d'ajouter les scripts de ressources au projet (pour Dev-C++ il faut les ajouter par le menu « Projects » puis « Projects Options » puis par le bouton « Load Resource », pour C++ Builder il faut tout simplement ajouter les fichiers au projet). La compilation des ressources sera dans ce cas transparente.

Nous allons en voir deux dans ce chapitre : les icônes et les menus.

III. Le script de ressources

Nous allons rependre l'exemple du chapitre 3 - Les commandes et notifications. Nous allons d'abord ajouter des icônes dans les ressources de l'exécutable. Créons tout d'abord un fichier script de ressources nommé « resource.rc » avec le texte suivant :

Sélectionnez
1 ICON icone.ico
2 ICON autre.ico

Chaque ligne est composée de trois éléments, le premier est un identificateur, le second le type de ressource (ICON pour une icône dans l'exemple) le dernier est le contenu, dans le cas de l'exemple ce sera le contenu d'un fichier. Ils doivent bien sûr exister. Les ayant mis dans le même dossier que les fichiers source du programme, je n'ai pas indiqué le chemin pour les localiser. S'ils se trouvent ailleurs, il faudra bien sûr mettre le chemin (j'ai dû tout de même le mettre pour la compilation avec Dev-C++ malgré qu'ils se trouvaient dans le même dossier).

Certains compilateurs de ressources demandent d'inclure les fichiers entêtes de Windows qui contiennent la définition des identificateurs utilisés dans le fichier script de ressources.

Il peut y avoir des différences notables entre les différents compilateurs de ressources. Je vous invite à consulter l'aide de celui que vous utilisez si vous rencontrez des problèmes de compilation.

Je le répète une dernière fois, ce fichier devra être ajouté au projet si vous possédez un EDI ou compilé à l'aide d'un compilateur de ressources et lié à l'exécutable si vous n'avez pas d'EDI.

À ce stade, si vous compilez et que vous visualisez le fichier exécutable dans l'explorateur de fichiers, il devrait avoir la première icône de la liste (ce n'est pas forcement vrai si vous utilisez un EDI, ils peuvent ajouter automatiquement une icône aux ressources de l'exécutable).

Nous allons maintenant mettre la deuxième icône comme icône de la fenêtre (celle qui se trouve à gauche sur la barre de titre) :

Sélectionnez
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASS wc;

    wc.style = 0 ;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = NULL;
    wc.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(2));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  "MyMenu";
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

Pour cela on affecte le champ hIcon de la classe de fenêtre avec une icône que l'on charge avec la fonction LoadIcon. Elle reçoit comme paramètre le handle d'instance de l'application, pour son second paramètre elle attend un pointeur sur une chaîne de caractères identifiant l'icône. Dans le script de ressource, nous l'avons identifié avec une valeur numérique. C'est avec la macro MAKEINTRESOURCE que nous faisons cette conversion.

Nous aurions pu les identifier avec des valeurs littérales :

Sélectionnez
ICONE1 ICON icone.ico
ICONE2 ICON autre.ico

Dans ce cas LoadIcon aurait reçu un pointeur sur la chaîne de caractère identifiant l'icône dans son second paramètre.

Sélectionnez
     wc.hIcon = LoadIcon(hinstance,"ICONE2");

IV. Ressources de type menu

Toujours avec le même exemple, nous allons supprimer le menu que l'on avait créé par programme au chapitre 3 et le créer avec un script de ressources :

Sélectionnez
LEMENU MENU
BEGIN
  POPUP "Fichier"
    BEGIN
       MENUITEM "&Nouveau", IDM_NEW
       MENUITEM SEPARATOR
       MENUITEM "&Quitter", IDM_QUIT
    END
  POPUP "Aide"
    BEGIN
       MENUITEM "À propos...", IDM_ABOUT
    END
END

Nous retrouvons là aussi nos trois éléments : l'identificateur de la ressource (littérale dans cet exemple), suivi de son type (MENU : barre de menu) puis de son contenu. Ici le contenu n'est pas un fichier, mais un script de construction de barre de menu, balisé par les mots BEGIN et END. Elle possède deux sous-menus (POPUP : menu déroulant) « fichier » et « Aide ». Le premier possède trois options (MENUITEM) « &Nouveau », SEPARATOR et « &Quitter ». La liste des options d'un sous-menu est aussi balisée par les mots BEGIN et END. Chaque option doit être identifiée par une constante numérique. Cette constante sera envoyée à la procédure de fenêtre quand l'option du menu sera actionnée. Il est préférable de les nommer pour plus de clarté dans le code. Comme elles devront être connues du script de ressource et du fichier source du programme, nous les définissons dans un fichier entête séparé, nommé dans cet exemple « resource.h » :

Sélectionnez
#define IDM_QUIT  1
#define IDM_NEW   2
#define IDM_ABOUT 3

Ce fichier devra donc être inclus dans le fichier source et dans le fichier script de resource (voir code à la fin de ce document). Le menu sera ensuite affecté à la classe de fenêtre. Ceci en affectant au champ lpszMenuName de la classe de fenêtre, un pointeur sur une chaîne de caractères contenant le nom littéral du menu :

Sélectionnez
    wc.lpszMenuName =  "LEMENU";

C'est tout pour sa construction. La récupération des actions du menu se fait sur réception des messages WM_COMMAND de la même manière qu'au chapitre 3 :

Sélectionnez
    switch (uMsg)
    {
        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
            /*...         */

Vous avez du remarquer que j'ai mis le caractère '&' dans le texte des options de menu « &Nouveau » et « &Quitter ». Ce signe sert à souligner le caractère qui le suit et à donner la possibilité d'activer l'option correspondante avec la touche du clavier correspondant à ce caractère. Ceci uniquement quand le popup menu est ouvert. À ne pas confondre avec les accélérateurs clavier que l'on verra lors d'un chapitre ultérieur.

Nous aurons l'occasion de revenir sur les différents types de ressources au cours des chapitres suivants.

V. Code complet

V-A. resource.h

Sélectionnez
#define IDM_QUIT  1
#define IDM_NEW   2
#define IDM_ABOUT 3

V-B. resource.rc

Sélectionnez
#include <windows.h>

#include "resource.h"

1 ICON icone.ico
2 ICON autre.ico

LEMENU MENU
BEGIN
  POPUP "Fichier"
    BEGIN
       MENUITEM "&Nouveau", IDM_NEW
       MENUITEM SEPARATOR
       MENUITEM "&Quitter", IDM_QUIT
    END
  POPUP "Aide"
    BEGIN
       MENUITEM "À propos...", IDM_ABOUT
    END
END

V-C. winmain.c

Sélectionnez
#include <windows.h>

#include "resource.h"

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc;

    hinst = hinstance;

    wc.style = 0 ;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(2));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  "LEMENU";
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
                                                   NULL, NULL, hinstance, NULL);
    if (!hwnd)  return FALSE;

    ShowWindow(hwnd, nCmdShow);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HWND hEdit;
    static BOOL EditNotChg = TRUE;
    switch (uMsg)
    {
        case WM_CREATE:
            {
             HFONT hFont;
             hEdit = CreateWindow("edit", "Texte",
               WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
                                            0, 0, 0, 0, hwnd, NULL, hinst, NULL);
             hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
             SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
             SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
                                                                MAKELONG(5, 5));
             return 0;
            }

        case WM_CLOSE:
            if(EditNotChg ||
               MessageBox(hwnd,"Le texte a été modifié.\r\nÊtes-vous sûr de \
vouloir fermer l'application ?"
                           ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                                                            DestroyWindow(hwnd);
            return 0;

        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);

            if(LOWORD(wParam) == IDM_NEW)
                if(EditNotChg ||
                  MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer votre travail ?"
                            ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                      {
                              SendMessage(hEdit,WM_SETTEXT,0,(long)"");
                              EditNotChg = TRUE;
                      }

            if(LOWORD(wParam) == IDM_ABOUT)
                     MessageBox(hwnd,"Mon beau programme !","A propos !",MB_OK);

            if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;

            return 0;

        case WM_SIZE:
             MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
             return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }


Win32 Tutorial - Recommended Books and References

theForger's Win32 API Programming Tutorial

Home

Basics
  1. Getting Started
  2. A Simple Window
  3. Handling Messages
  4. The Message Loop
  5. Using Resources
  6. Menus and Icons
  7. Dialog Boxes
  8. Modeless Dialogs
  9. Standard Controls
  10. Dialog FAQ
Creating a simple application
  1. Creating controls at runtime
  2. Files and the common dialogs
  3. Tool and Status bars
  4. Multiple Document Interface
Graphics Device Interface
  1. Bitmaps and Device Contexts
  2. Transparency
  3. Timers and Animation
  4. Text, Fonts and Colours
Tools and Documentation
  1. References
  2. Free Visual C++
Appendices
  1. Solutions to Common Errors
  2. API vs. MFC
  3. Resource file notes

Recommended Books and References

Books

If you expect anyone online to treat you with respect while you are learning, you NEED to get a good book to learn from. We're here to provide direction and explain things that need explaining, not to be your librarian or teach you step by step.

You can find more recommended books and links to buy at the #Winprog Store.

Programming Windows
by Charles Petzold. The book to get on Win32 API. If you want to write programs using just the API (which is what this tutorial covers), you need this book.
Programming Windows with MFC
by Jeff Prosise. If you want to venture into MFC (AFTER becoming fully accustomed to using the Win32 API), this is the book for you. If you don't like MFC but intend on getting a job doing windows developement, get this anyway, it's better to know than not.
Programming Applications for Windows
by Jeffrey Richter. Not for newbies, if you want to be up on managing processes and threads, dlls, windows memory management, exception handling, and hooking into the system, then this is the book for you.
Visual C++ Windows Shell Programming
by Dino Esposito. For anyone interested in the visual and user-friendly aspects of windows, this book covers writing extentions to the windows shell, working efficiently with files and drag and drop, customizing the taskbar and windows explorer, and numerous other tricks. Well worthwhile for anyone writing GUI apps in windows.
Network Programming for Microsoft Windows
Up to date information on network programming, including NetBIOS, mailslots and pipes, and of course the ever important windows sockets, complete with winsock2 and raw sockets. Also contains specific information on the various windows platforms including 2000 and CE.

Links

MSDN Online
This site has references for all imaginable Microsoft technologies, including full Win32 API and MFC documentation. If this didn't come with your compiler (ie. VC++) then the completely free online site will provide you with the required information. People will get really pissed off if you ask questions you could answer by doing a simple search on MSDN.
#winprog homepage
See FAQ and Store

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Tutorial: Getting Started

Getting Started

What this tutorial is all about

This tutorial is intended to present to you the basics (and common extras) of writing programs using the Win32 API. The language used is C, most C++ compilers will compile it as well. As a matter of fact, most of the information is applicable to any language that can access the API, inlcuding Java, Assembly and Visual Basic. I will not however present any code relating to these languages and you're on your own in that regard, but several people have previously used this document in said languages with quite a bit of success.

This tutorial will not teach you the C language, nor will it tell you how to run your particular compiler (Borland C++, Visual C++, LCC-Win32, etc...) I will however take a few moments in the appendix to provide some notes on using the compilers I have knowledge of.

If you don't know what a macro or a typedef are, or how a switch() statement works, then turn back now and read a good book or tutorial on the C language first.

Important notes

Sometimes throughout the text I will indicate certain things are IMPORANT to read. Because they screw up so many people, if you don't read it, you'll likely get caught too. The first one is this:

The source provided in the example ZIP file is not optional! I don't include all the code in the text itself, only that which is relevant to whatever I'm currently discussing. In order to see how this code fits in with the rest of the program, you must take a look at the source provided in the ZIP file.

And here's the second one:

Read the whole thing! If you have a question during one section of the tutorial just have a little patience and it might just be answered later on. If you just can't stand the thought of not knowing, at least skim or search (yes computers can do that) the rest of the document before asking the nice folks on IRC or by email.

Another thing to remember is that a question you might have about subject A might end up being answered in a discussion of B or C, or maybe L. So just look around a little.

Ok I think that's all the ranting I have to do for the moment, lets try some actual code.

The simplest Win32 program

If you are a complete beginner lets make sure you are capable of compiling a basic windows application. Slap the following code into your compiler and if all goes well you should get one of the lamest programs ever written.

Remember to compile this as C, not C++. It probably doesn't matter, but since all the code here is C only, it makes sense to start off on the right track. In most cases, all this requires if you add your code to a .c file instead of a .cpp file. If all of this hurts your head, just call the file test.c and be done with it.

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    MessageBox(NULL, "Goodbye, cruel world!", "Note", MB_OK);
    return 0;
}

If that doesn't work, your first step is to read whatever errors you get and if you don't understand them, look them up in the help or whatever documents accompany your compiler. Make sure you have specified a Win32 GUI (NOT "Console") project/makefile/target, whatever applies to your compiler. Unfortunately I can't help much with this part either, as errors and how to fix them vary from compiler to compiler (and person to person).

You may get some warnings about you not using the parameters supplied to WinMain(). This is OK. Now that we've established you can in fact compile a program, lets go through that little bit of code....

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
   LPSTR lpCmdLine, int nCmdShow)
WinMain() is windows equivalent of main() from DOS or UNIX. This is where your program starts execution. The parameters are as follows:
HINSTANCE hInstance
Handle to the programs executable module (the .exe file in memory)
HINSTANCE hPrevInstance
Always NULL for Win32 programs.
LPSTR lpCmdLine
The command line arguments as a single string. NOT including the program name.
int nCmdShow
An integer value which may be passed to ShowWindow(). We'll get to this later.

hInstance is used for things like loading resources and any other task which is performed on a per-module basis. A module is either the EXE or a DLL loaded into your program. For most (if not all) of this tutorial, there will only be one module to worry about, the EXE.

hPrevInstance used to be the handle to the previously run instance of your program (if any) in Win16. This no longer applies. In Win32 you ignore this parameter.

Calling Conventions

WINAPI specifies the calling convention and is defined as _stdcall. If you don't know what this means, don't worry about it as it will not really affect us for the scope of this tutorial. Just remember that it's needed here.

Win32 Data Types

You will find that many of the normal keywords or types have windows specific definitions, UINT for unsigned int, LPSTR for char* etc... Which you choose is really up to you. If you are more comfortable using char* instead of LPSTR, feel free to do so. Just make sure that you know what a type is before you substitute something else.

Just remember a few things and they will be easy to interpret. An LP prefix stands for Long Pointer. In Win32 the Long part is obsolete so don't worry about it. And if you don't know what a pointer is, you can either 1) Go find a book or tutorial on C, or 2) just go ahead anyway and screw up a lot. I'd really recommend #1, but most people go with #2 (I would :). But don't say I didn't warn you.

Next thing is a C following a LP indicates a const pointer. LPCSTR indicates a pointer to a const string, one that can not or will not be modified. LPSTR on the other hand is not const and may be changed.

You might also see a T mixed in there. Don't worry about this for now, unless you are intentionally working with Unicode, it means nothing.

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Tutorial: A Simple Window

A Simple Window

Sometimes people come on IRC and ask "How do I make a window?"...Well it's not entirely that simple I'm afraid. It's not difficult once you know what you're doing but there are quite a few things you need to do to get a window to show up; And they're more than can be simply explained over a chat room, or a quick note.

I always liked to do things first and learn them later...so here is the code to a simple window which will be explained shortly.

#include <windows.h>

const char g_szClassName[] = "myWindowClass";

// Step 4: the Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    //Step 1: Registering the Window Class
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // Step 2: Creating the Window
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // Step 3: The Message Loop
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}
For most part this is the simplest windows program you can write that actually creates a functional window, a mere 70 or so lines. If you got the first example to compile then this one should work with no problems.

Step 1: Registering the Window Class

A Window Class stores information about a type of window, including it's Window Procedure which controls the window, the small and large icons for the window, and the background color. This way, you can register a class once, and create as many windows as you want from it, without having to specify all those attributes over and over. Most of the attributes you set in the window class can be changed on a per-window basis if desired.

A Window Class has NOTHING to do with C++ classes.

const char g_szClassName[] = "myWindowClass";
The variable above stores the name of our window class, we will use it shortly to register our window class with the system.
    WNDCLASSEX wc;

    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

This is the code we use in WinMain() to register our window class. We fill out the members of a WNDCLASSEX structure and call RegisterClassEx().

The members of the struct affect the window class as follows:

cbSize
The size of the structure.
style
Class Styles (CS_*), not to be confused with Window Styles (WS_*) This can usually be set to 0.
lpfnWndProc
Pointer to the window procedure for this window class.
cbClsExtra
Amount of extra data allocated for this class in memory. Usually 0.
cbWndExtra
Amount of extra data allocated in memory per window of this type. Usually 0.
hInstance
Handle to application instance (that we got in the first parameter of WinMain()).
hIcon
Large (usually 32x32) icon shown when the user presses Alt+Tab.
hCursor
Cursor that will be displayed over our window.
hbrBackground
Background Brush to set the color of our window.
lpszMenuName
Name of a menu resource to use for the windows with this class.
lpszClassName
Name to identify the class with.
hIconSm
Small (usually 16x16) icon to show in the taskbar and in the top left corner of the window.
Don't worry if that doesn't make much sense to you yet, the various parts that count will be explained more later. Another thing to remember is to not try and remember this stuff. I rarely (never) memorize structs, or function parameters, this is a waste of effort and, more importantly, time. If you know the functions you need to call then it is a matter of seconds to look up the exact parameters in your help files. If you don't have help files, get them. You are lost without. Eventually you will come to know the parameters to the functions you use most.

We then call RegisterClassEx() and check for failure, if it fails we pop up a message which says so and abort the program by returning from the WinMain() function.

Step 2: Creating the Window

Once the class is registered, we can create a window with it. You should look up the paramters for CreateWindowEx() (as you should ALWAYS do when using a new API call), but I'll explain them briefly here.

    HWND hwnd;

    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

The first parameter (WS_EX_CLIENTEDGE) is the extended windows style, in this case I have set it to give it a sunken inner border around the window. Set it to 0 if you'd like to see the difference. Also play with other values to see what they do.

Next we have the class name (g_szClassName), this tells the system what kind of window to create. Since we want to create a window from the class we just registered, we use the name of that class. After that we specify our window name or title which is the text that will be displayed in the Caption, or Title Bar on our window.

The parameter we have as WS_OVERLAPPEDWINDOW is the Window Style parameter. There are quite a few of these and you should look them up and experiment to find out what they do. These will be covered more later.

The next four parameters (CW_USEDEFAULT, CW_USEDEFAULT, 320, 240) are the X and Y co-ordinates for the top left corner of your window, and the width and height of the window. I've set the X and Y values to CW_USEDEFAULT to let windows choose where on the screen to put the window. Remeber that the left of the screen is an X value of zero and it increases to the right; The top of the screen is a Y value of zero which increases towards the bottom. The units are pixels, which is the smallest unit a screen can display at a given resolution.

Next (NULL, NULL, g_hInst, NULL) we have the Parent Window handle, the menu handle, the application instance handle, and a pointer to window creation data. In windows, the windows on your screen are arranged in a heirarchy of parent and child windows. When you see a button on a window, the button is the Child and it is contained within the window that is it's Parent. In this example, the parent handle is NULL because we have no parent, this is our main or Top Level window. The menu is NULL for now since we don't have one yet. The instance handle is set to the value that is passed in as the first parameter to WinMain(). The creation data (which I almost never use) that can be used to send additional data to the window that is being created is also NULL.

If you're wondering what this magic NULL is, it's simply defined as 0 (zero). Actually, in C it's defined as ((void*)0), since it's intended for use with pointers. Therefore you will possibly get warnings if you use NULL for integer values, depending on your compiler and the warning level settings. You can choose to ignore the warnings, or just use 0 instead.

Number one cause of people not knowing what the heck is wrong with their programs is probably that they didn't check the return values of their calls to see if they failed or not. CreateWindow() will fail at some point even if you're an experianced coder, simply because there are lots of mistakes that are easy to make. Untill you learn how to quickly identify those mistakes, at least give yourself the chance of figuring out where things go wrong, and Always check return values!

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

After we've created the window and checked to make sure we have a valid handle we show the window, using the last parameter in WinMain() and then update it to ensure that it has properly redrawn itself on the screen.

   ShowWindow(hwnd, nCmdShow);
   UpdateWindow(hwnd);

The nCmdShow parameter is optional, you could simply pass in SW_SHOWNORMAL all the time and be done with it. However using the parameter passed into WinMain() gives whoever is running your program to specify whether or not they want your window to start off visible, maximized, minimized, etc... You will find options for these in the properties of windows shortcuts, and this parameter is how the choice is carried out.

Step 3: The Message Loop

This is the heart of the whole program, pretty much everything that your program does passes through this point of control.

    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;

GetMessage() gets a message from your application's message queue. Any time the user moves the mouse, types on the keyboard, clicks on your window's menu, or does any number of other things, messages are generated by the system and entered into your program's message queue. By calling GetMessage() you are requesting the next available message to be removed from the queue and returned to you for processing. If there is no message, GetMessage() Blocks. If you are unfamiliar with the term, it means that it waits untill there is a message, and then returns it to you.

TranslateMessage() does some additional processing on keyboard events like generating WM_CHAR messages to go along with WM_KEYDOWN messages. Finally DispatchMessage() sends the message out to the window that the message was sent to. This could be our main window or it could be another one, or a control, and in some cases a window that was created behind the scenes by the sytem or another program. This isn't something you need to worry about because all we are concerned with is that we get the message and send it out, the system takes care of the rest making sure it gets to the proper window.

Step 4: the Window Procedure

If the message loop is the heart of the program, the window procedure is the brain. This is where all the messages that are sent to our window get processed.
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

The window procedure is called for each message, the HWND parameter is the handle of your window, the one that the message applies to. This is important since you might have two or more windows of the same class and they will use the same window procedure (WndProc()). The difference is that the parameter hwnd will be different depending on which window it is. For example when we get the WM_CLOSE message we destroy the window. Since we use the window handle that we received as the first paramter, any other windows will not be affected, only the one that the message was intended for.

WM_CLOSE is sent when the user presses the Close Button [x] or types Alt-F4. This will cause the window to be destroyed by default, but I like to handle it explicitly, since this is the perfect spot to do cleanup checks, or ask the user to save files etc. before exiting the program.

When we call DestroyWindow() the system sends the WM_DESTROY message to the window getting destroyed, in this case it's our window, and then destroys any remaining child windows before finally removing our window from the system. Since this is the only window in our program, we are all done and we want the program to exit, so we call PostQuitMessage(). This posts the WM_QUIT message to the message loop. We never receive this message, because it causes GetMessage() to return FALSE, and as you'll see in our message loop code, when that happens we stop processing messages and return the final result code, the wParam of WM_QUIT which happens to be the value we passed into PostQuitMessage(). The return value is only really useful if your program is designed to be called by another program and you want to return a specific value.

Step 5: There is no Step 5

Phew. Well that's it! If I haven't explained stuff clearly enough yet, just hang in there and hopefully things will become more clear as we get into more usefull programs.

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Tutorial: Handling Messages

Handling Messages

Alright, we've got a window, but it doesn't do anything except what DefWindowProc() allows it to, like be sized, maximised, etc... Not really all that exciting.

In the next section I am going to show you how to modify what you already have to do something new. This way I can just tell you "Handle this message, and do this in it..." and you will know what I mean and be able to do so without seeing an entire example. That's the hope anyway, so pay attention :P

Okay for starters take the example code for the last window we worked on and make sure it compiles and runs as expected. Then you can either keep working on it for the next little bit or copy it to a new project to modify.

We're going to add the capability to show the user what the name of our program is when they click on our window. Not very exciting, it's basically to get the hang of handling messages. Lets look at what we have in our WndProc():

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}
If we want to handle mouse clicks, we need to add a WM_LBUTTONDOWN handler (or WM_RBUTTONDOWN, WM_MBUTTONDOWN, for right and middle clicks respectively).

If I or someone else refers to handling a message they mean to add it into the WndProc() of your window class as follows:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   switch(msg)
   {
      case WM_LBUTTONDOWN:    // <-
                              // <-     we just added this stuff
      break;                  // <-
      case WM_CLOSE:
         DestroyWindow(hwnd);
      break;
      case WM_DESTROY:
         PostQuitMessage(0);
      break;
      default:
         return DefWindowProc(hwnd, msg, wParam, lParam);
   }
   return 0;
}
The order in which you handle your messages rarely matters. Just make sure you've got your break; after each one. As you can see we added another case into our switch(). Now we want something to happen when we get to this part of our program.

First I will present the code we want to add (that will show the user the filename of our program) and then I will integrate it into our program. Later on I will probably just show you the code and let you integrate it into your program. This is of course better for me as I don't have to type as much and it's better for you because you will be able to add the code into ANY program and not just the ones I present. If you aren't sure how to do it, look at the example zip file included with the section.

GetModuleFileName(hInstance, szFileName, MAX_PATH);
MessageBox(hwnd, szFileName, "This program is:", MB_OK | MB_ICONINFORMATION);
Now this code does not stand on it's own, it can't just be slapped into our code any old place. We specifically want it to run when the user clicks the mouse button so this is how I would merge this small bit of code into our skeleton program:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_LBUTTONDOWN:
// BEGIN NEW CODE
        {
            char szFileName[MAX_PATH];
            HINSTANCE hInstance = GetModuleHandle(NULL);

            GetModuleFileName(hInstance, szFileName, MAX_PATH);
            MessageBox(hwnd, szFileName, "This program is:", MB_OK | MB_ICONINFORMATION);
        }
// END NEW CODE
        break;
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

Note the new set of curly braces {} . These are required when declaring variables inside a switch() statement. This should be basic C knowledge but I thought I should point it out anyway for those of you doing things the hard way.

So if you've added in that code, compile it now. If it works, click on the window and you should see a box with the name of the .exe pop up.

You'll notice we've added two variables, hInstance and szFileName. Look up GetModuleFileName() and you will see that the first parameter is a HINSTANCE refering to the executable module (our program, the .exe file). Where do we get such a thing? GetModuleHandle() is the answer. The references for GetModuleHandle() indicate that passing in NULL will return us "a handle to the file used to create the calling process", which is exactly what we need, the HINSTANCE just mentioned. Putting all this information together we end up with the following declaration:

HINSTANCE hInstance = GetModuleHandle(NULL);

Now on to the second parameter, again turning to our trusty reference manual, we see that it is " a pointer to a buffer that receives the path and file name of the specified module" and the data type is LPTSTR (or LPSTR if your references are old). Since LPSTR is equivalent to char* we can declare an array of char's like this:

char szFileName[MAX_PATH];

MAX_PATH is a handy macro included via <windows.h> that is defined to the maximum length of a buffer needed to store a filename under Win32. We also pass MAX_PATH to GetModuleFileName() so it knows the size of the buffer.

After GetModuleFileName() is called, the buffer szFileName will be filled with a null terminated string containing the name of our .exe file. We pass this value to MessageBox() as an easy way of displaying it to the user.

So if you've added in that code, compile it now. If it works, click on the window and you should see a box with the name of the .exe pop up.

If it doesn't work, here's the full code to the program. Compare it to what you have and see what, if any, mistakes you made.

#include <windows.h>

const char g_szClassName[] = "myWindowClass";

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_LBUTTONDOWN:
        {
            char szFileName[MAX_PATH];
            HINSTANCE hInstance = GetModuleHandle(NULL);

            GetModuleFileName(hInstance, szFileName, MAX_PATH);
            MessageBox(hwnd, szFileName, "This program is:", MB_OK | MB_ICONINFORMATION);
        }
        break;
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Tutorial: Understanding the Message Loop

Understanding the Message Loop

Understanding the message loop and entire message sending structure of windows programs is essential in order to write anything but the most trivial programs. Now that we've tried out message handling a little, we should look a little deeper into the whole process, as things can get very confusing later on if you don't understand why things happen the way they do.

What is a Message?

A message is an integer value. If you look up in your header files (which is good and common practice when investigating the workings of API's) you can find things like:
#define WM_INITDIALOG                   0x0110
#define WM_COMMAND                      0x0111

#define WM_LBUTTONDOWN                  0x0201
...and so on. Messages are used to communicate pretty much everything in windows at least on basic levels. If you want a window or control (which is just a specialized window) to do something you send it a message. If another window wants you to do something it sends you a message. If an event happens such as the user typing on the keyboard, moving the mouse, clicking a button, then messages are sent by the system to the windows affected. If you are one of those windows, you handle the message and act accordingly.

Each windows message may have up to two parameters, wParam and lParam. Originally wParam was 16 bit and lParam was 32 bit, but in Win32 they are both 32 bit. Not every message uses these parameters, and each message uses them differently. For example the WM_CLOSE message doesn't use either, and you should ignore them both. The WM_COMMAND message uses both, wParam contains two values, HIWORD(wParam) is the notification message (if applicable) and LOWORD(wParam) is the control or menu id that sent the message. lParam is the HWND (window handle) to the control which sent the message or NULL if the messages isn't from a control.

HIWORD() and LOWORD() are macros defined by windows that single out the two high bytes (High Word) of a 32 bit value (0xFFFF0000) and the low word (0x0000FFFF) respectively. In Win32 a WORD is a 16bit value, making DWORD (or Double Word) a 32bit value.

To send a message you can use PostMessage() or SendMessage(). PostMessage() puts the message into the Message Queue and returns immediatly. That means once the call to PostMessage() is done the message may or may not have been processed yet. SendMessage() sends the message directly to the window and does not return untill the window has finished processing it. If we wanted to close a window we could send it a WM_CLOSE message like this PostMessage(hwnd, WM_CLOSE, 0, 0); which would have the same effect as clicking on the [x] button on the top of the window. Notice that wParam and lParam are both 0. This is because, as mentioned, they aren't used for WM_CLOSE.

Dialogs

Once you begin to use dialog boxes, you will need to send messages to the controls in order to communicate with them. You can do this either by using GetDlgItem() first to get the handle to the control using the ID and then use SendMessage(), OR you can use SendDlgItemMessage() which combines the steps. You give it a window handle and a child ID and it will get the child handle, and then send it the message. SendDlgItemMessage() and similar APIs like GetDlgItemText() will work on all windows, not just dialog boxes.

What is the Message Queue

Lets say you were busy handling the WM_PAINT message and suddenly the user types a bunch of stuff on the keyboard. What should happen? Should you be interrupted in your drawing to handle the keys or should the keys just be discarded? Wrong! Obviously neither of these options is reasonable, so we have the message queue, when messages are posted they are added to the message queue and when you handle them they are removed. This ensure that you aren't going to miss messages, if you are handling one, the others will be queued up untill you get to them.

What is a Message Loop

while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
    TranslateMessage(&Msg);
    DispatchMessage(&Msg);
}
  1. The message loop calls GetMessage(), which looks in your message queue. If the message queue is empty your program basically stops and waits for one (it Blocks).
  2. When an event occures causing a message to be added to the queue (for example the system registers a mouse click) GetMessages() returns a positive value indicating there is a message to be processed, and that it has filled in the members of the MSG structure we passed it. It returns 0 if it hits WM_QUIT, and a negative value if an error occured.
  3. We take the message (in the Msg variable) and pass it to TranslateMessage(), this does a bit of additional processing, translating virtual key messages into character messages. This step is actually optional, but certain things won't work if it's not there.
  4. Once that's done we pass the message to DispatchMessage(). What DispatchMessage() does is take the message, checks which window it is for and then looks up the Window Procedure for the window. It then calls that procedure, sending as parameters the handle of the window, the message, and wParam and lParam.
  5. In your window procedure you check the message and it's parameters, and do whatever you want with them! If you aren't handling the specific message, you almost always call DefWindowProc() which will perform the default actions for you (which often means it does nothing).
  6. Once you have finished processing the message, your windows procedure returns, DispatchMessage() returns, and we go back to the beginning of the loop.

This is a very important concept for windows programs. Your window procedure is not magically called by the system, in effect you call it yourself indirectly by calling DispatchMessage(). If you wanted, you could use GetWindowLong() on the window handle that the message is destined for to look up the window's procedure and call it directly!

while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
    WNDPROC fWndProc = (WNDPROC)GetWindowLong(Msg.hwnd, GWL_WNDPROC);
    fWndProc(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
}

I tried this with the previous example code, and it does work, however there are various issues such as Unicode/ANSI translation, calling timer callbacks and so forth that this method will not account for, and very likely will break all but trivial applications. So do it to try it, but don't do it in real code :)

Notice that we use GetWindowLong() to retreive the window procedure associated with the window. Why don't we just call our WndProc() directly? Well our message loop is responsible for ALL of the windows in our program, this includes things like buttons and list boxes that have their own window procedures, so we need to make sure that we call the right procedure for the window. Since more than one window can use the same window procedure, the first parameter (the handle to the window) is used to tell the window procedure which window the message is intended for.

As you can see, your application spends the majority of it's time spinning round and round in this message loop, where you joyfully send out messages to the happy windows that will process them. But what do you do when you want your program to exit? Since we're using a while() loop, if GetMessage() were to return FALSE (aka 0), the loop would end and we would reach the end of our WinMain() thus exiting the program. This is exactly what PostQuitMessage() accomplishes. It places a WM_QUIT message into the queue, and instead of returning a positive value, GetMessage() fills in the Msg structure and returns 0. At this point, the wParam member of Msg contains the value that you passed to PostQuitMessage() and you can either ignore it, or return it from WinMain() which will then be used as the exit code when the process terminates.

IMPORTANT: GetMessage() will return -1 if it encounters an error. Make sure you remember this, or it will catch you out at some point... even though GetMessage() is defined as returning a BOOL, it can return values other than TRUE or FALSE, since BOOL is defined as UINT (unsigned int). The following are examples of code that may seem to work, but will not process certian conditions correctly:

    while(GetMessage(&Msg, NULL, 0, 0))

    while(GetMessage(&Msg, NULL, 0, 0) != 0)

    while(GetMessage(&Msg, NULL, 0, 0) == TRUE)
The above are all wrong! It may be of note that I used to use the first of these throughout the tutorial, since as I just mentioned, it works fine as long as GetMessage() never fails, which when your code is correct it won't. However I failed to take into consideration that if you're reading this, your code probably won't be correct a lot of the time, and GetMessage() will fail at some point :) I've gone through and corrected this, but forgive me if I've missed a few spots.
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
This, or code that has the same effect should always be used.

I hope you now have a better understanding of the windows message loop, if not, do not fear, things will make more sense once you have been using them for a while.

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Tutorial: Using Resources

Using Resources

You may also want to refer to the Appendices at the end of this tutorial for more information on resources with VC++ and BC++.

Before we get any deeper I will cover the topic of resources so that I won't have to re-write it for each section.You don't actually need to compile the stuff in this section, it's as example only.

Resources are pre-defined bits of data stored in binary format inside your executable file. You create resources in a resources script, a file with an extension of ".rc". comercial compilers will have a visual resource editor which allows you to create resources without manually editing this file but sometimes editing it is the only way to go, especially if your compiler has no visual editor, it sucks, or doesn't support the exact feature you need.

Unfortunately different compiler suites handle resources differently. I will do the best I can to explain the common features needed to work with resources in general.

The resource editor included with MSVC++ makes it very difficult to edit the resources manually, since it enforces a proprietary format on them, and will totally mangle the file if you save one that you had created by hand. In general you shouldn't bother with creating .rc files from scratch, but knowing how to modify them manually can be very useful. Another annoyance is that MSVC++ will by default name the resource header file "resource.h" even if you wanted to call it something else. I will go with this for the sake of simplicity in this document, but will show you how to change this in the appendix on compilers.

First lets take a very simple resource script, with a single icon.

#include "resource.h"

IDI_MYICON ICON "my_icon.ico"

That's the entire file. IDI_MYICON is the identifier of the resource, ICON is the type and "my_icon.ico" is the name of the external file which contains it. This should work on any compiler.

Now what about this #include "resource.h" ? Well your program needs a way to identify the icon, and the best way to do that is to assign it a unique ID (IDI_MYICON). We can do this by creating the file "resource.h" and including it in both our resource script, and our source file.

#define IDI_MYICON  101

As you can see, we've assigned IDI_MYICON the value of 101. We could just forget about the identifier and use 101 wherever we need to reference the icon, but IDI_MYICON is a lot clearer as to what you are refering too, and easier to remember when you have large number of resources.

Now lets say we add a MENU resource:

#include "resource.h"

IDI_MYICON ICON "my_icon.ico"

IDR_MYMENU MENU
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "E&xit", ID_FILE_EXIT
    END
END
Again IDR_MYMENU is the name of the resource and MENU is the type. Now a fine point, see the BEGIN and END up there? Some resource editors or compilers use { in place of BEGIN and } in place of END. If your compiler supports both feel free to pick which one you use. If it only supports one or the other, you will need to make the necessary replacements to get it to work.

We've also added a new identifier, ID_FILE_EXIT, so we need to add this to our resource header file, resource.h, in order to use it in our program.

#define IDI_MYICON  101

#define ID_FILE_EXIT	4001

Generating and keeping track of all these ids can become a real chore with large projects, that's why most people use a visual resource editor which takes care of all this for you. They still screw up from time to time, and you could end up with multiple items with the same ID or a similar problem, and it's good to be able to go in and fix it yourself.

Now an example of how to use a resource in your program.

    HICON hMyIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MYICON));
The first parameter of LoadIcon() and many other resource using functions is the handle to the current instance (which we are given in WinMain() and can also be retreived by using GetModuleHandle() as demonstrated in previous sections). The second is the identifier of the resource.

You're probably wondering what's up with MAKEINTRESOURCE() and possibly wondering why LoadIcon() takes a parameter of type LPCTSTR instead of say UINT when we're passing it an ID. All MAKEINTRESOURCE() does is cast from an integer (what our ID is) to LPCTSTR, which LoadIcon() expects. This brings us to the second way of identifying resources, and that's with strings. Almost nobody does this any more, so I won't go into details, but basically if you don't use #define to assign an integer value to your resources then the name is interpreted as a string, and can be referenced in your program like this:

    HICON hMyIcon = LoadIcon(hInstance, "MYICON");

LoadIcon() and other resource loading APIs can tell the difference between an integer passed in and a pointer to a string passed in by checking the high word of the value. If it's 0 (as would be the case of any integer with a value less than or equal to 65535) then it assumes it is a resource ID. This effectively limits your resources to using IDs below 65535, which unless you have a whole lot of resources, should not be a problem. If it's not 0 then it assumes the value is a pointer, and looks up the resource by name. Never rely on an API to do this unless it is explicitely stated in the documentation.

For example, this doesn't work for menu commands like ID_FILE_EXIT, since they can only be integers.

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Tutorial: Menus and Icons

Menus and Icons

This is just a small section to show how to add basic menus to your window. Usually you use a pre-made menu resource. This will be in an .rc file and will be compiled and linked into your .exe. This is rather compiler specific, commercial compilers will have a resource editor that you can use to create your menus, but for this example I will show the text of the .rc file so you can add it in manually. I usually have an .h file as well which is included in both my .rc file and my .c source files. This file contains the identifiers for controls and menu items etc.

For this example you can start with the window code from simple_window and add this code into it as instructed.

First the .h file. Usually called "resource.h"

#define IDR_MYMENU 101
#define IDI_MYICON 201

#define ID_FILE_EXIT 9001
#define ID_STUFF_GO 9002
Not much there, but our menu will be pretty simple. The names and values here are up to you for the choosing. Now we write our .rc file.
#include "resource.h"

IDR_MYMENU MENU
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "E&xit", ID_FILE_EXIT
    END

    POPUP "&Stuff"
    BEGIN
        MENUITEM "&Go", ID_STUFF_GO
        MENUITEM "G&o somewhere else", 0, GRAYED
    END
END

IDI_MYICON ICON "menu_one.ico"

You will want to add the .rc file to your project or makefile depending on what tools you are using.

You also want to #include "resource.h" in your source file (.c) so that the menu command identifiers and the menu resource id will be defined.

The easiest way to attach the menu and icon to your window is to specify them when you register the window class, like this:

    wc.lpszMenuName  = MAKEINTRESOURCE(IDR_MYMENU);

    wc.hIcon  = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON));
    wc.hIconSm  = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON), IMAGE_ICON, 16, 16, 0);

Change that and see what happens. Your window should now have a File and Stuff menu with the respective items underneath. That is assuming your .rc file was properly compiled and linked into your program. (again, see compiler notes)

The icon in the top left of the window and on the task bar should now display the small custom icon that we specified. If you hit Alt-Tab, the large version of the icon should be displayed in the application list.

I've used LoadIcon() to load the large icon because it's simpler, however it will only load icons at the default resolution of 32x32, so in order to load the smaller image, we need to use LoadImage(). Be aware that icon files and resources can contain multiple images, and in this case the ones I've supplied contain the two sizes that I'm loading.

Example: menu_two

An alternative to using a menu resource is to create one on the fly (or when your program runs). This is a bit more work programming wise, but adds flexibility and is sometimes necessary.

You can also use icons that aren't stored as resources, you could choose to store your icon as a seperate file and load it at runtime. This would also give you the option of allowing the user to select an icon of their choice with the common dialogs discussed later, or something to that effect.

Start again from simple_window without the .h or .rc added. Now we will handle the WM_CREATE message and add a menu to our window.

#define ID_FILE_EXIT 9001
#define ID_STUFF_GO 9002
Put these two id's at the top of your .c file this time, underneath your #includes. Next we add the following code into our WM_CREATE handler.
    case WM_CREATE:
    {
        HMENU hMenu, hSubMenu;
        HICON hIcon, hIconSm;

        hMenu = CreateMenu();

        hSubMenu = CreatePopupMenu();
        AppendMenu(hSubMenu, MF_STRING, ID_FILE_EXIT, "E&xit");
        AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&File");

        hSubMenu = CreatePopupMenu();
        AppendMenu(hSubMenu, MF_STRING, ID_STUFF_GO, "&Go");
        AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&Stuff");

        SetMenu(hwnd, hMenu);

        hIcon = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 32, 32, LR_LOADFROMFILE);
        if(hIcon)
            SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
        else
            MessageBox(hwnd, "Could not load large icon!", "Error", MB_OK | MB_ICONERROR);

        hIconSm = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        if(hIconSm)
            SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm);
        else
            MessageBox(hwnd, "Could not load small icon!", "Error", MB_OK | MB_ICONERROR);
    }
    break;

This creates a menu almost the same as the one we had in the resource and attaches it to our window. A menu that is assigned to a window is automatically removed when the program terminates, so we don't need to worry about getting rid of it later. If we did though, we could use GetMenu() and DestroyMenu().

The code for the icons is pretty simple, we call LoadImage() twice, to load the icon as both a 16x16 size and a 32x32 size. We can't use LoadIcon() at all because it will only load resources, not files. We specify NULL for the instance handle parameter because we aren't loading a resource from our module, and instead of a resource ID we pass in the name of the icon file we want to load. Finally, we pass in the LR_LOADFROMFILE flag to indicate that we want the function to treat the string we give it as a filename and not a resource name.

If each call succeeds we assign the icon handle to our window with WM_SETICON, and if it fails we pop up a message box letting us know something went wrong.

NOTE: that the LoadImage() calls will fail if the icon file isn't in the current working directory of the program. If you are using VC++ and you run the program from the IDE, the current working directory will be the one the project file is in. However if you run the program from the Debug or Release directories from explorer or the command shell, then you'll need to copy the icon file into that directory in order for the program to find it. If all else fails, specify the full path to the icon, "C:\\Path\\To\\Icon.ico".

Okay now that we have our menu, we need to make it do something. This is pretty simple, all we need to do is handle the WM_COMMAND message. Also we'll need to check which command we are getting and act accordingly. Now our WndProc() should look something like this.

LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
    switch(Message)
    {
        case WM_CREATE:
        {
            HMENU hMenu, hSubMenu;

            hMenu = CreateMenu();

            hSubMenu = CreatePopupMenu();
            AppendMenu(hSubMenu, MF_STRING, ID_FILE_EXIT, "E&xit");
            AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&File");

            hSubMenu = CreatePopupMenu();
            AppendMenu(hSubMenu, MF_STRING, ID_STUFF_GO, "&Go");
            AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&Stuff");

            SetMenu(hwnd, hMenu);

            hIcon = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 32, 32, LR_LOADFROMFILE);
            if(hIcon)
                SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
            else
                MessageBox(hwnd, "Could not load large icon!", "Error", MB_OK | MB_ICONERROR);

            hIconSm = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
            if(hIconSm)
                SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm);
            else
                MessageBox(hwnd, "Could not load small icon!", "Error", MB_OK | MB_ICONERROR);
        }
        break;
        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case ID_FILE_EXIT:

                break;
                case ID_STUFF_GO:

                break;
            }
        break;
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, Message, wParam, lParam);
    }
    return 0;
}
As you can see we've got our WM_COMMAND all set up, and it even has another switch() in it. This switch()'s on the value of the low word of wParam, which in the case of WM_COMMAND contains the control or menu id that sent the message.

We obviously want the Exit menu item to close the program. So in the WM_COMMAND, ID_FILE_EXIT handler you can use the following code to do just that.

PostMessage(hwnd, WM_CLOSE, 0, 0);

Your WM_COMMAND handler should now look like this:

    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
            case ID_FILE_EXIT:
                PostMessage(hwnd, WM_CLOSE, 0, 0);
            break;
            case ID_STUFF_GO:

            break;
        }
    break;
I leave it up to you to make the other menu command ID_STUFF_GO do something.

The program file icon

You may have noticed that the menu_one.exe file now shows up as the custom icon we added as a resource, whereas the menu_two.exe file does not, since we are loading an external file. Windows Explorer simply displays the first icon (numerically by ID) in the program files resources, so since we only have one icon, that's what it is displaying. If you want to be sure that a certain icon is displayed with your program file, simply add it as a resource and assign it a very low ID... like 1. You don't even need to refer to the file in your program, and you can load completely different icons for your windows if you choose.

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Tutorial: Dialogs, GUI coders best friend

Dialogs, GUI coders best friend

There's hardly a windows program out there that doesn't use dialog boxes. Just go File -> Open in any text editor or any other kind of editor for that matter and voila, you are presented with a dialog box, one that probably allows you to select a file to be opened.

Dialogs aren't limited to the standard open file ones, they can look like and do whatever you choose. The attractive point of dialogs is that they provide a quick way to arrange and create a GUI (Graphic User Interface) and even some default processing, cutting down on the amount of code you must write.

One thing to remember is that dialogs are just windows. The difference between a dialog and a "normal" window is that the system does some additional default processing for dialogs, such as creating and initialising controls, and handling tab order. Nearly all APIs that are applicable to "normal" windows will work just as well on dialogs, and vice versa!

The first step is to create the dialog resource. As with any resource how you do this will depend on your compiler/IDE. Here I will show you the plain text of the dilaog in the .rc file and let you incorporate it into your project.

IDD_ABOUT DIALOG DISCARDABLE  0, 0, 239, 66
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "My About Box"
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON   "&OK",IDOK,174,18,50,14
    PUSHBUTTON      "&Cancel",IDCANCEL,174,35,50,14
    GROUPBOX        "About this program...",IDC_STATIC,7,7,225,52
    CTEXT           "An example program showing how to use Dialog Boxes\r\n\r\nby theForger",
                    IDC_STATIC,16,18,144,33
END
On this first line, IDD_ABOUTDLG is the id of the resource. DIALOG is the resource type, and the four number are the Left, Top, Width and Height co-ordinates. These ARE NOT PIXELS, they are in Dialog Units, which are based on the size of the font used by the system (and chosen by the user). If you have a large font selected, the dialog will be large, if you use a smaller font, the dialog will be that much smaller. This is important as it makes sure that all of the controls are the proper size to display their text in the current font. You can convert dialog units to pixels at runtime using MapDialogRect(). DISCARDABLE tells the system it may swap the resource memory to disk when it's not being used in order to conserve system resources (essentially pointless).

The second line starts with STYLE and follows with the window styles that will be used to create the dialog. These should be explained under CreateWindow() in your help files. In order to use the predefined constants you may need to add #include "windows.h" to your .rc file, or in the case of VC++, winres.h or afxres.h will do. If you use the resource editor these files will certainly be included automatically if needed.

The CAPTION line should be self explanitory.

The FONT line specifies the size and name of the font you wish to use for this dialog box. This might not end up exactly the same on each computer as different people will have different fonts and may have specified different font sizes. You usually don't need to worry about that though.

Now we have the list of controls to create on the dialog

    DEFPUSHBUTTON   "&OK",IDOK,174,18,50,14

Here's the line for the OK button. The & in this case like with menus underlines the next letter "O", so that by pressing Alt+O the user can activate this control (part of the default processing I mentioned). IDOK is the control identifier. IDOK is pre-defined so we don't need to #define it ourselves. The four numbers at the end are the left, top, width and height, all in dialog units.

This information should be purely academic, as you almost always use a resource editor to create dialogs, but knowing how to do it from text is sometimes necessary, expecially if you have no visual editor.

Two of the controls have an ID of IDC_STATIC (which is -1), this is used to indicate we never need to access them, so they have no need of an identifier. However it doesn't hurt to give them an ID and your resource editor might do so automatically.

The "\r\n" in the text of the static control is a CR-LF pair, the way windows represents a new line.

So! Having added that to your .rc file we need to write a Dialog Procedure to process message for this box. Don't worry this is nothing new, it's practicly the same as our main Window Procedure (but not exactly).

BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
    switch(Message)
    {
        case WM_INITDIALOG:

        return TRUE;
        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case IDOK:
                    EndDialog(hwnd, IDOK);
                break;
                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                break;
            }
        break;
        default:
            return FALSE;
    }
    return TRUE;
}
There are a few important differences between a dialog procedure and window procedure. One is that you DO NOT call DefWindowProc() for message you don't handle. With dialogs this is done automatically for you (and will really screw things up if you do it).

Secondly, in general you return FALSE for messages you don't process, and TRUE for messages you do process, UNLESS the message specifies you return something else. Note that this is what we do above, the default is to do nothing and return FALSE, while messages we do handle break the switch() and return TRUE.

Thirdy, You do not call DestroyWindow() to close a dialog, you call EndDialog(). The second paramter is the value that is returned to whatever code called DialogBox().

Finally, instead of handling WM_CREATE, you handle WM_INITDIALOG to do any processing that needs to be done before the dialog appears, and then return TRUE to have the keyboard focus set to the default control. (You can actually handle WM_CREATE as well, but it is sent BEFORE any of the controls have been created, so you can't access them. In WM_INITDIALOG the controls have already been created).

Enough chit-chat, lets create it....

case WM_COMMAND:
    switch(LOWORD(wParam))
    {
        case ID_HELP_ABOUT:
        {
            int ret = DialogBox(GetModuleHandle(NULL),
                MAKEINTRESOURCE(IDD_ABOUT), hwnd, AboutDlgProc);
            if(ret == IDOK){
                MessageBox(hwnd, "Dialog exited with IDOK.", "Notice",
                    MB_OK | MB_ICONINFORMATION);
            }
            else if(ret == IDCANCEL){
                MessageBox(hwnd, "Dialog exited with IDCANCEL.", "Notice",
                    MB_OK | MB_ICONINFORMATION);
            }
            else if(ret == -1){
                MessageBox(hwnd, "Dialog failed!", "Error",
                    MB_OK | MB_ICONINFORMATION);
            }
        }
        break;
        // Other menu commands...
    }
break;

This is the code I used to create my about box, you can probably guess that this is to be merged into your WM_COMMAND handler, if you aren't clear on this aspect, you might want to review the section on menus. ID_HELP_ABOUT is the identifier of my Help -> About menu item.

Since we want the menu on our main window to create the dialog, we obviously want to put this code in the WndProc() of our main window, not the dialog proc.

Now I stored the return value from the call to DialogBox(), this is just so you can observe the effects of pressing the two buttons, hitting Esc, Enter etc... from inside the dialog. It also illustrates how to use the return value from a dialog box to check for success, failure, a users choice, or whatever other information you choose to send back to the caller from the Dialog Procedure.

DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_ABOUT), hwnd, AboutDlgProc);
This is the only important part, and you can choose to put it wherever in your code that you want the dialog to come up. IDD_ABOUT is the id of the dialog resource. hwnd is the handle to the parent window of the dialog. AboutDlgProc() is of course the dialog procedure to use to control the dialog.

That's it! Sit IDD_UBU, sit.

A perticularly astute reader might eventually wonder, if DialogBox() doesn't return untill the dialog closes we can't process messages while it's up, so how does it work? Well the nifty thing about DialogBox() is that it has it's own message loop, so while the dialog is displayed, our message loop is out of the picture and the default loop is handled by windows. This loop also takes care of fun things like moving the keyboard focus from control to control when you press Tab.

Another effect of using DialogBox is that your main window is disabled untill the dialog is dismissed. Sometimes this is what we want, and sometimes it isn't, such as when we want to use a dialog as a floating toolbar. In this case we want to be able to interact with both out dialog and our main window, and this will be the focus of the next section.

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Tutorial: Modeless Dialogs

Modeless Dialogs

Now we take a look at CreateDialog(), DialogBox()'s sister function. The difference is that while DialogBox() implements it's own message loop and does not return untill the dialog is closed, CreateDialog() acts more like a window created with CreateWindowEx() in that it returns immediately and depends on your message loop to pump the messages as it does for your main window. This is termed Modeless, whereas DialogBox() creates Modal dialogs.

You can create the dialog resource just like you did for the last dialog example, you might also want to set the "Tool window" extended style to give it's title bar the typical smaller caption of toolbars. The dialog resource I created follows:

IDD_TOOLBAR DIALOGEX 0, 0, 98, 52
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION
EXSTYLE WS_EX_TOOLWINDOW
CAPTION "My Dialog Toolbar"
FONT 8, "MS Sans Serif"
BEGIN
    PUSHBUTTON      "&Press This Button",IDC_PRESS,7,7,84,14
    PUSHBUTTON      "&Or This One",IDC_OTHER,7,31,84,14
END

You may notice that the resource editor has replaced DIALOG with DIALOGEX indicating we want to set an EXSTYLE on our dialog.

Next we want to create the dialog when our program runs, I want the dialog visible right away so we do this in WM_CREATE. We also want to declare a global variable to hold the window handle returned from CreateDialog() so that we can use it later. DialogBox() didn't return a handle to us since when DialogBox() returns the window has been destroyed.

HWND g_hToolbar = NULL;

    case WM_CREATE:
        g_hToolbar = CreateDialog(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_TOOLBAR),
            hwnd, ToolDlgProc);
        if(g_hToolbar != NULL)
        {
            ShowWindow(g_hToolbar, SW_SHOW);
        }
        else
        {
            MessageBox(hwnd, "CreateDialog returned NULL", "Warning!",
                MB_OK | MB_ICONINFORMATION);
        }
    break;

We check the return value, which is ALWAYS a good idea, and if it's valid (not NULL) we show the window with ShowWindow(), with DialogBox() this isn't necessary since the system calls ShowWindow() for us.

Now we need a dialog procedure for our toolbar.

BOOL CALLBACK ToolDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
    switch(Message)
    {
        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case IDC_PRESS:
                    MessageBox(hwnd, "Hi!", "This is a message",
                        MB_OK | MB_ICONEXCLAMATION);
                break;
                case IDC_OTHER:
                    MessageBox(hwnd, "Bye!", "This is also a message",
                        MB_OK | MB_ICONEXCLAMATION);
                break;
            }
        break;
        default:
            return FALSE;
    }
    return TRUE;
}

Most of the same message handling rules apply to dialogs created with CreateDialog() as with DialogBox(), don't call DefWindowProc(), return FALSE for messages you don't handle and TRUE for those you do.

One change is that we don't call EndDialog() for modeless dialogs, we can use DestroyWindow() just like for regular windows. In this case I destroy the dialog when the main window is destroyed. In the main window's WndProc()...

    case WM_DESTROY:
        DestroyWindow(g_hToolbar);
        PostQuitMessage(0);
    break;

Last but not least, we want to be able to display and hide our toolbar whenever we choose so I've added two commands to my menu to do this, and handled them so:

    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
            case ID_DIALOG_SHOW:
                ShowWindow(g_hToolbar, SW_SHOW);
            break;
            case ID_DIALOG_HIDE:
                ShowWindow(g_hToolbar, SW_HIDE);
            break;
            //... other command handlers
        }
    break;

You should be able to create your own menu using the resource editor or manually, but if not (as always) take a look at the example project dlg_two provided with the tutorial.

Now when you run the program, you should be able to access both the dialog window, and main window at the same time.

If you've run the program at this point and tried tabbing between the two buttons, you have probably noticed it doesn't work, neither does hitting Alt-P or Alt-O to activate the buttons. Why not? Whereas DialogBox() implements it's own message loop and handles these events by default, CreateDialog() does not. We can do it ourselves though, by calling IsDialogMessage() in our message loop which will do the default processing for us.

    while(GetMessage(&Msg, NULL, 0, 0))
    {
        if(!IsDialogMessage(g_hToolbar, &Msg))
        {
            TranslateMessage(&Msg);
            DispatchMessage(&Msg);
        }
    }

Here we first pass the message to IsDialogMessage(), if the message is destined for our toolbar (indicated by the window handle we pass in) the system will perform the default processing and return TRUE. Is this case the message has already been handled so we don't want to call TranslateMessage() or DispatchMessage(). If the message is for another window we process as usual.

It's also worth noting that IsDialogMessage() can also be used with windows that aren't dialogs in order to to give them dialog-like behaviour. Remember, a dialog is a window, and most (if not all) dialog APIs will work on any window.

And that is pretty much all there is to modeless dialogs! One issue that may arise is if you have more than one toolbar... what do you do? Well one possible solution is to have a list (either an array, an STL std::list, or similar) and loop through it in your message loop passing each handle to IsDialogMessage() until the right one is found, and if none, do the regular processing. This is a generic programming problem, not one that is Win32 related, and is left as an excersize to the reader.

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Standard Controls: Button, Edit, List Box, Static

Standard Controls: Button, Edit, List Box

I realize I've already used buttons in previous examples, so you should already be more or less familiar with them, however I figured that since I was using them in this example I might as well add it to the title for the sake of being complete.

Controls

One thing to remember about controls is that they are just windows. Like any other window they have a window procedure, a window class etc... that is registered by the system. Anything you can do with a normal window you can do with a control.

Messages

As you may remember from our earlier discussion of the message loop, windows communicate using messages, you send them to get a control to do something, and when an event occurs on the control it will send you a notification message back. For the standard controls this notification will be a WM_COMMAND message as we've already seen with buttons and menus. For the Common Controls which I may get to later, it will be WM_NOTIFY.

The messages you send are widely varied between each control, and each control has it's own set of messages. Once in a while the same message will be used for more than one kind of control, but in general they will only work on the control they are intended for. This is especially annoying with the listbox and combobox messages (LB_* and CB_*) which although they perform nearly identical tasks, are NOT interchangeable, and I accidently get them mixed up more than I'd like to admit :)

On the other hand, generic messages like WM_SETTEXT are supported by almost all controls. A control is just a window after all.

You can send messages using the SendMessage() API, and use GetDlgItem() to retreive the handle to the control, or you can use SendDlgItemMessage() which does both steps for you, the results of both methods are identical.

Edits

One of the most commonly used controls in the windows environment, the EDIT control, is used to allow the user to enter, modify, copy, etc... text. Windows Notepad is little more than a plain old window with a big edit control inside it.

Here is the code used to interface with the edit control in this example:

    SetDlgItemText(hwnd, IDC_TEXT, "This is a string");
That's all it takes to change the text contained in the control (this can be used for pretty much any control that has a text value associated with it, STATICs, BUTTONs and so on).

Retreiving the text from the control is easy as well, although slightly more work than setting it...

    int len = GetWindowTextLength(GetDlgItem(hwnd, IDC_TEXT));
    if(len > 0)
    {
        int i;
        char* buf;

        buf = (char*)GlobalAlloc(GPTR, len + 1);
        GetDlgItemText(hwnd, IDC_TEXT, buf, len + 1);

        //... do stuff with text ...

        GlobalFree((HANDLE)buf);
    }

First of all, we need to allocate some memory to store the string in, it won't just return us a pointer to the string already in memory. In order to do this, we first need to know how much memory to allocate. There isn't a GetDlgItemTextLength(), but there is a GetWindowTextLength(), so all we need to do is get the handle to the control using GetDlgItem().

Now that we have the length, we can allocate some memory. Here I've added a check to see if there is any text to begin with, since most likely you don't want to be working with an empty string... sometimes you might, but that's up to you. Assuming that there is something there to work with, we call GlobalAlloc() to allocate some memory. GlobalAlloc() as I've used it here is equivalent to calloc(), if you're used to DOS/UNIX coding. It allocates some memory, initializes it's contents to 0 and returns a pointer to that memory. There are different flags you can pass as the first paramter to make it behave differently for different purposes, but this is the only way I will be using it in this tutorial.

Note that I added 1 to the length in two places, what's up with that? Well, GetWindowTextLength() returns the number of characters of text the control contains NOT INCLUDING the null terminator. This means that if we were to allocate a string without adding 1, the text would fit, but the null terminator would overflow the memory block, possibly corrupting other data, causing an access violation, or any number of other bad things. You must be careful when dealing with string sizes in windows, some APIs and messages expect text lengths to include the null and others don't, always read the docs thoroughly.

If I lost you talking about null terminators, please refer to a basic C book or tutorial which discusses strings.

Finally we can call GetDlgItemText() to retrieve the contents of the control into the memory buffer that we've just allocated. This call expects the size of the buffer INCLUDING the null terminator. The return value, which we ignored here, is the number of characters copied, NOT including the null terminator.... fun eh? :)

After we're all done using the text (which we'll get to in a moment), we need to free up the memory that we allocated so that it doesn't leak out and drip down onto the CPU and short circuit your computer. To accomplish this, we simply call GlobalFree() and pass in our pointer.

You may be or become aware of a second set of APIs named LocalAlloc(), LocalFree(), etc... which are legacy APIs from 16-bit windows. In Win32, the Local* and Global* memory functions are identical.

Edits with Numbers

Entering text is all well and fine, but what if you want the user to enter in a number? This is a pretty common task, and fortunately there is an API to make this simpler, which takes care of all the memory allocation, as well as converting the string to an integer value.

    BOOL bSuccess;
    int nTimes = GetDlgItemInt(hwnd, IDC_NUMBER, &bSuccess, FALSE);

GetDlgItemInt() works much like GetDlgItemText(), except that instead of copying the string to a buffer, it converts it internally into an integer and returns the value to you. The third parameter is optional, and takes a pointer to a BOOL. Since the function returns 0 on failure, there is no way to tell just from that whether or not the function failed or the user just entered 0. If you are fine with a value of 0 in the event of an error, then feel free to ignore this parameter.

Another useful feature is the ES_NUMBER style for edit controls, which allows only the characters 0 through 9 to be entered. This is very handy if you only want positive integers, otherwise it's not much good, since you can't enter any other characters, including - (minus) . (decimel) or , (comma).

List Boxes

Another handy control is the list box. This is the last standard control that I'm going to cover for now, cause frankly they aren't that interesting, and if you aren't bored yet well, I am :)

Adding Items

The first thing you'll want to do with a listbox is add items to it.

    int index = SendDlgItemMessage(hwnd, IDC_LIST, LB_ADDSTRING, 0, (LPARAM)"Hi there!");
As you can see, this is a pretty simple task. If the listbox has the LBS_SORT style, the new item will be added in alphabetical order, otherwise it will just be added to the end of the list.

This message returns the index of the new item either way, and we can use this to perform other tasks on the item, such as associating some data with it. Usually this will be things like a pointer to a struct containing more information, or maybe an ID that you will use to identify the item, it's up to you.

    SendDlgItemMessage(hwnd, IDC_LIST, LB_SETITEMDATA, (WPARAM)index, (LPARAM)nTimes);

Notifications

The whole purpose of listboxes is to allow the user to select things from a list. Now sometimes we don't care when exactly they do this, for example with our Remove button, we don't need to know when the selection changes right away, we just check when the user activates the button.

However, sometimes you want to be able to do something right away, perhaps display different or updated information based on what items are selected. In order to do this we need to handle the notification messages that the listbox passes to us. In this case, we are interested in LBN_SELCHANGE, which tells us that the selection has been modified by the user. LBN_SELCHANGE is sent via WM_COMMAND but unlike handling the WM_COMMAND from buttons or menu's, which are usually only in response to a click, a list box sends WM_COMMAND for various reasons, and we need a second check to find out what it's telling us. The Notification Code is passed as the HIWORD of wParam, the other half of the parameter that gave us the control ID in the first place.

    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
            case IDC_LIST:
                // It's our listbox, check the notification code
                switch(HIWORD(wParam))
                {
                    case LBN_SELCHANGE:
                        // Selection changed, do stuff here.
                    break;
                }
            break;
            // ... other controls
        }
    break;

Getting Data from the ListBox

Now that we know the selection has changed, or at the request of the user, we need to get the selection from the listbox and do something useful with it.

In this example I've used a multiselection list box, so getting the list of selected items is a little trickier. If it were a single selection listbox, than you could simply send LB_GETCURSEL to retrieve the item index.

First we need to get the number of selected items, so that we can allocate a buffer to save the indexes in.

    HWND hList = GetDlgItem(hwnd, IDC_LIST);
    int count = SendMessage(hList, LB_GETSELCOUNT, 0, 0);

Then we allocate a buffer based on the number of items, and send LB_GETSELITEMS to fill in the array.

    int *buf = GlobalAlloc(GPTR, sizeof(int) * count);
    SendMessage(hList, LB_GETSELITEMS, (WPARAM)count, (LPARAM)buf);

    // ... Do stuff with indexes

    GlobalFree(buf);

In this example, buf[0] is the first index, and so on up to buf[count - 1].

One of the things you would likely want to do with this list of indexes, is retreive the data associated with each item, and do some processing with it. This is just as simple as setting the data was originally, we just send another message.

    int data = SendMessage(hList, LB_GETITEMDATA, (WPARAM)index, 0);

If the data was some other type of value (anything that is 32-bits) you could simply cast to the appropriate type. For example if you stored HBITMAPs instead of ints...

    HBITMAP hData = (HBITMAP)SendMessage(hList, LB_GETITEMDATA, (WPARAM)index, 0);

Statics

Like buttons, static controls are largely trivial, but for the sake or being complete I include them here. Static controls are usually just that, static, meaning they don't change or really do anything else very special, they're largely for displaying text to the user. However you can make them slightly more useful by assigning them a unique ID (VC++ assigns a default ID of IDC_STATIC, which is -1 and effectively means "No ID") and then setting the text at runtime to present dynamic data to the user.

In the example code, I use one to display the data of the item selected in the list box, assuming one and only one is selected.

    SetDlgItemInt(hwnd, IDC_SHOWCOUNT, data, FALSE);

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Tutorial: Dialog FAQ

Dialog FAQ

Now don't get me wrong, this is a Tutorial, not a Reference, but some questions people ask SO often that I figured I might as well include them here.

Changing Colours

In general, the only reason you'd want to do this is to simulate an link on a dialog box or some similar task, because otherwise you're probably just making your program ugly and hard on the eyes if you go adding a bunch of colors to the dialogs, but that doesn't stop people from doing it, and there are actually a few valid reasons, so here you go :)

Windows sends a variety of messages related to colours to your dialog procedure, and by handling these messages you can change what colour certain things are displayed in. For example, to change the color of the dialog box itself, you can handle WM_CTLCOLORDLG, to change the colors for a static control you handle WM_CTLCOLORSTATIC and so on.

First you can create a brush to use to paint the background and store it for later. the WM_CTLCOLORDLG and related messages will get called often during the course of your program, and if you created a new brush every time, eventually you would use up a great deal of RAM with dead brushes. This way we have more control, and we can delete it when the dialog is destroyed and we know we won't need it any more.

    HBRUSH g_hbrBackground = CreateSolidBrush(RGB(0, 0, 0));

    case WM_CTLCOLORDLG:
        return (LONG)g_hbrBackground;
    case WM_CTLCOLORSTATIC:
    {
        HDC hdcStatic = (HDC)wParam;
        SetTextColor(hdcStatic, RGB(255, 255, 255));
        SetBkMode(hdcStatic, TRANSPARENT);
        return (LONG)g_hbrBackground;
    }
    break;

Notice the line that sets the background mode to transparent... if you leave this line off the background will be filled in with the brush you specify, but when the control draws the text it will get written over with the default background color! Setting the text drawing mode to transparent fixes this problem. The other option would be to SetBkColor() to the same color as our background brush, but I like this solution better.

Changing the colors on pretty much any other standard control works the same way, just look up the WM_CTLCOLOR* messages in your Win32 reference. Note that an edit control will send a WM_CTLCOLORSTATIC if it is read only, and WM_CTLCOLOREDIT if it isn't.

If you have more than one static (or other) control that you want to be different colours, then you'll need to check the ID of the control you are getting the message from and change your colours based on that. You are passed the HWND of the control in lParam, and you can get the ID of the control from this using GetDlgCtlrID(). Note that static controls are all given a default ID of IDC_STATIC (-1) by the resource editor, so if you want to be able to tell them apart you'll need to assign them new IDs.

Giving the Dialog an Icon

A fairly simple task, you just need to send WM_SETICON to your dialog. Since windows uses two icons however, you need to call it twice, once for the small icon displayed in the corner of the window, and once for the large one displayed when you hit Alt-Tab. You can just send the same handle both times unless you have multi-sized icons.

To just set the default application icon, you can use the following code:

    SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION)));
    SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION)));
When you substitute your own icon resource for the default, remember to change the HINSTANCE parameter of LoadIcon() to your applications instance (you can get it by calling GetModuleHandle(NULL) if you don't have it stored from WinMain()).

Why Doesn't my Combo Box Work?

An all-too-common problem is people adding a combo box to their dialog and they can't figure out why the list doesn't show up when they run their program and click the little arrow. This is understandable, since the solution is not very intuitive.

When you create a combo box and specify it's height, you are actually specifying the entire height, drop-down list included, NOT the height of the control when it is collapsed which is determined by the system based on the size of the font used.

For example, giving the control a height of 100 pixels, the system sizes the control itself to the default (lets say 30 in this case), and when you click on the arrow, the drop down list would be 70 pixels high, for a total of 100 pixels.

If you use the VC++ resource editor to place the combo on your dialog, you will notice you can't size it vertically. Unless you click on the arrow in the editor, and it will then change the focus rectangle to indicate you are sizing the dropdown list, and you can set the height to whatever you want.

What about all the other controls!

Well I could give examples of all of the other controls, but that's what MSDN and Petzold are for :) If you can't figure out how to use them, you probably need to re-read some parts of this tutorial, or get a book which will explain things more thouroughly.

I'd like to give you a link to a useful page on MSDN, but Microsoft seems to be determined to prevent me from doing so as links to individual MSDN pages either change rapidly or don't work period. Therefor you'll probably have to figure out how to get around yourself, look around for sections like User Interface Services, and Windows Controls, sometimes under a Platform SDK section.

MSDN - Windows Controls

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


App Part 1: Creating controls at runtime

I thought that since an example on creating controls on the fly, although usefull, would be quite pointless unless the application actually did something, so in this entry I will start the workings of a text editor and build upon it untill we reach a nearly useful program that supports opening, editing and saving text documents.

The first step, which this particular page covers will be simply creating the window and the EDIT control that will serve as the center of our program.

Starting with the skeleton code from the Simple Window application we add a #define as our control ID and the following two message handlers into our window procedure:

#define IDC_MAIN_EDIT	101

    case WM_CREATE:
    {
        HFONT hfDefault;
        HWND hEdit;

        hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "",
            WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,
            0, 0, 100, 100, hwnd, (HMENU)IDC_MAIN_EDIT, GetModuleHandle(NULL), NULL);
        if(hEdit == NULL)
            MessageBox(hwnd, "Could not create edit box.", "Error", MB_OK | MB_ICONERROR);

        hfDefault = GetStockObject(DEFAULT_GUI_FONT);
        SendMessage(hEdit, WM_SETFONT, (WPARAM)hfDefault, MAKELPARAM(FALSE, 0));
    }
    break;
    case WM_SIZE:
    {
        HWND hEdit;
        RECT rcClient;

        GetClientRect(hwnd, &rcClient);

        hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
        SetWindowPos(hEdit, NULL, 0, 0, rcClient.right, rcClient.bottom, SWP_NOZORDER);
    }
    break;

Creating controls

Creating controls, like creating any other window, is done through the CreateWindowEx() API. We pass in pre-registered class that we want, in this case the "EDIT" control class, and we get a standard edit control window. When using dialogs to create our controls, we are basically writing a list of controls to create so that then you call DialogBox() or CreateDialog() the system reads through the list of controls in the dialog resource and calls CreateWindowEx() for each one with the position and styles that were defined in the resource.
    hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "",
        WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,
        0, 0, 100, 100, hwnd, (HMENU)IDC_MAIN_EDIT, GetModuleHandle(NULL), NULL);
    if(hEdit == NULL)
        MessageBox(hwnd, "Could not create edit box.", "Error", MB_OK | MB_ICONERROR);
You can see that this call to CreateWindowEx() specifies quite a few styles, and it's not uncommon to have many more, especially for the Common Controls which have a hearty list of options. The first 4 WS_ styles should be fairly obvious, we are creating the control as a child of our window, we want it to be visible, and have vertical and horizontal scroll bars.

The 3 styles that are specific to EDIT controls (ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL) specify that the EDIT control should contain multiple lines of text, and scroll automatically as you type beyond the bottom and right hand side of the control respectively.

The regular window styles (WS_*) are listed here. And the extended windows styles (WS_EX_*) are explained under the CreateWindowEx() reference in MSDN, where you can also find links to the styles that are specific to each control (ES_* in our case of the edit control).

We have specified our window handle as the parent of the control, and assigned it an ID of IDC_MAIN_EDIT which we'll use later on to refer to the control just as you would if the control had been created on a dialog. The position and size parameters don't mean too much at the moment since we will be resizing the control dynamically in the WM_SIZE message so that it will always fit our window.

Sizing of dynamically created controls

Generally if your window is sizeable you'll want some code to resize or reposition the controls you created within it so that they are always layed out properly.
    GetClientRect(hwnd, &rcClient);

    hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
    SetWindowPos(hEdit, NULL, 0, 0, rcClient.right, rcClient.bottom, SWP_NOZORDER);

Since we only have one control for now, the task is relatively simple. We use GetClientRect() to get the dimentions of the Client Area of the window, the big (up untill now) blank area that does not include the borders, menu or caption. This will fill in our RECT structure with the value, the left and top values will always be 0, so you can usually just ignore them. The right and bottom values will give you the width and the hight of the client area.

Next we simply get a handle to our edit control using GetDlgItem() which works just as well on regular windows as it does on dialogs, and the call SetWindowPos() to move and size it to fill the entire client area. You can of course change the values you pass into SetWindowPos() to do something like only fill half of the window's height, leaving the bottom free to place other controls.

Creating other controls at runtime

I'm not going to give examples of dynamically creating the other controls like LISTBOX, BUTTON, etc... because it's basically the same and it gets kinda boring after a while :) If you follow the links into MSDN above, or look in your local Win32 API reference you will be able to find all of the information needed to create any of the other standard controls.

We'll be doing more of this with the common controls in the next couple of sections so you'll get more practice eventually.

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


App Part 2: Using files and the common dialogs

The Common File Dialogs

The first step to opening or saving files is finding out the filename to use... of course you could always hard code the name of the file into your program, but honestly that doesn't make for very useful programs most of the time.

Since this is such a common task, there are predefined system dialogs that you can use to allow the user to select a file name. The most common open and save file dialogs are accessed through GetOpenFileName() and GetSaveFileName() respectively, both of which take an OPENFILENAME struct.

    OPENFILENAME ofn;
    char szFileName[MAX_PATH] = "";

    ZeroMemory(&ofn, sizeof(ofn));

    ofn.lStructSize = sizeof(ofn); // SEE NOTE BELOW
    ofn.hwndOwner = hwnd;
    ofn.lpstrFilter = "Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0";
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = MAX_PATH;
    ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
    ofn.lpstrDefExt = "txt";

    if(GetOpenFileName(&ofn))
    {
        // Do something usefull with the filename stored in szFileName
    }

Note that we call ZeroMemory() on the struct in order to initialise it to 0. This is generally a wise practice, as some APIs are very picky about members that you don't use being set to NULL. This way you don't need to explicitely set each member that you don't use.

You can easily find out the meanings of the various members by looking them up in your documentation. The lpstrFilter value points to a double-NULL terminated string, and you can see from the example that there are several "\0" throughout it, including one at the end... the compiler will add the second one at the end as it always does with string constants (that's what you generally don't need to put them in yourself). The NULLs in this string break it up into filters, each one is two parts. The first filter has the description "Text Files (*.txt)", the wildcard isn't required here I just put it in because I felt like it. The next part is the actual wildcard for the first filter, "*.txt". We do the same thing with the second filter except that this is a generic filter for all files. You can add as many different filters as you'd like.

The lpstrFile points to the buffer we have allocated to store the name of the file, since filenames can't be larger than MAX_PATH this is the value that I've chosen for the buffer size.

The flags indicate that the dialog should only allow the user to enter filenames that already exist (since we want to open them, not create them) and to hide the option to open the file in readonly mode, which we aren't going to support. Finally we provide a default extention, so if the user types in "foo" and the file is not found, it will try to open "foo.txt" before finally giving up.

To select a file for saving instead of opening, the code is nearly the same, except for calling GetSaveFileName() we need only change the flags member to options more suitable for saving.

    ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;

In this case we no longer want to require the file exist, but we do want the directory to exist since we aren't going to try and create it first. We'll also prompt the user if they select an existing file to make sure they want to overwrite it.

NOTE: MSDN States the following for the lStructSize member:

lStructSize
Specifies the length, in bytes, of the structure.

Windows NT 4.0: In an application that is compiled with WINVER and _WIN32_WINNT >= 0x0500, use OPENFILENAME_SIZE_VERSION_400 for this member.

Windows 2000/XP: Use sizeof (OPENFILENAME) for this parameter.

Basically what this means is that as of Windows 2000 they added some members to this struct, and so it's size changed. If the code above doesn't work for you it's possibly because the size that your compiler used and the size that your operating system (ie. Windows 98, Windows NT4) expected were different and so the call failed. If this happens, try using OPENFILENAME_SIZE_VERSION_400 instead of sizeof(ofn). Thanks to people that pointed this out to me.

Reading and Writing Files

In windows you have a few options as to how you want to access files. You can use the old io.h open()/read()/write(), you can use stdio.h fopen()/fread()/fwrite(), and if you are in C++ use can use iostreams.

However in windows all of these method ultimately call the Win32 API functions, which are what I will use here. If you are already comfortable using file IO with another method it should be fairly easy to pick up, or if you want simply use your method of choice to access files.

To open files, you can use OpenFile() or CreateFile(). MS recommends using only CreateFile() as OpenFile() is now "obsolete". CreateFile() is a much more versatile function and provides a great deal of control over the way you open files.

Reading

Say for example you have allowed the user to select a file using GetOpenFileName()...

BOOL LoadTextFileToEdit(HWND hEdit, LPCTSTR pszFileName)
{
    HANDLE hFile;
    BOOL bSuccess = FALSE;

    hFile = CreateFile(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, 0, NULL);
    if(hFile != INVALID_HANDLE_VALUE)
    {
        DWORD dwFileSize;

        dwFileSize = GetFileSize(hFile, NULL);
        if(dwFileSize != 0xFFFFFFFF)
        {
            LPSTR pszFileText;

            pszFileText = GlobalAlloc(GPTR, dwFileSize + 1);
            if(pszFileText != NULL)
            {
                DWORD dwRead;

                if(ReadFile(hFile, pszFileText, dwFileSize, &dwRead, NULL))
                {
                    pszFileText[dwFileSize] = 0; // Add null terminator
                    if(SetWindowText(hEdit, pszFileText))
                        bSuccess = TRUE; // It worked!
                }
                GlobalFree(pszFileText);
            }
        }
        CloseHandle(hFile);
    }
    return bSuccess;
}

There is a complete function to read a text file into an edit control. It takes as paramters the handle to the edit control and the name of the file to read in. This perticular function has a fair bit of error checking, file IO is one place where a lot of things can go wrong, and so you need to be on the lookout for errors.

Note the variable dwRead. We don't use it except as a paramter in ReadFile(). This parameter MUST be provided, the call will fail without it.

In the call to CreateFile() GENERIC_READ means we only want read access. FILE_SHARE_READ means it's okay if other programs open the file at the same time we do, but ONLY if they want to read as well, we don't want them writing to the file while we are reading it. And OPEN_EXISTING means only open the file if it already exists, don't create it, and don't overwrite it.

Once we've opened the file and chacked to see that CreateFile() succeeded, we check the size of the file so we'll know how much memory we need to allocate in order to read the entire thing. We then allocate the memory, check to make sure the allocation succeeded, and then call ReadFile() to load the contents from disk into our memory buffer. The API file functions have no concept of Text Files so they won't do things like read a single line of text, or add NULL terminators to the end of our strings. This is why we've allocated an extra byte and after we read in the file we add the NULL ourselves so that we can then pass the memory buffer as a string to SetWindowText().

Once all that has succeeded we set out success variable to TRUE, and clean up as we reach the end of the function, freeing the memory buffer and closing the file handle before finally returning to the caller.

Writing

BOOL SaveTextFileFromEdit(HWND hEdit, LPCTSTR pszFileName)
{
    HANDLE hFile;
    BOOL bSuccess = FALSE;

    hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, NULL,
        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile != INVALID_HANDLE_VALUE)
    {
        DWORD dwTextLength;

        dwTextLength = GetWindowTextLength(hEdit);
        // No need to bother if there's no text.
        if(dwTextLength > 0)
        {
            LPSTR pszText;
            DWORD dwBufferSize = dwTextLength + 1;

            pszText = GlobalAlloc(GPTR, dwBufferSize);
            if(pszText != NULL)
            {
                if(GetWindowText(hEdit, pszText, dwBufferSize))
                {
                    DWORD dwWritten;

                    if(WriteFile(hFile, pszText, dwTextLength, &dwWritten, NULL))
                        bSuccess = TRUE;
                }
                GlobalFree(pszText);
            }
        }
        CloseHandle(hFile);
    }
    return bSuccess;
}

Very similar to reading files, the function to write files has a few changes. First of all when we call CreateFile() we specify that we want Read access, that the file should always be created new (and if it exists it will be erased as it's opened) and that if it doesn't exist, it will be created with the normal file attributes.

Next we get the length of the memory buffer needed from the edit control, since this is the source of the data. Once we've allocated the memory, we request the string from the edit control using GetWindowText() and then write it to the file with WriteFile(). Again, like with ReadFile() the parameter that returns how much was actually written is required, even though we don't use it.

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


App Part 3: Tool and Status bars

An IMPORTANT Word on Common Controls

As with all common controls, you must call InitCommonControls() BEFORE you try and use them. You will need to #include <commctrl.h> in order to use this function and to get the functions and declarations necessary for use of the Common Controls. You will also need to add comctl32.lib to your linker settings if it is not already there. Note that InitCommonControls() is an older API, and for more control you can use InitCommonControlsEx() (aka InitCommonControlSex()) which is also required for the most recent common controls. However since I'm not using any of the advanced features, InitCommonControls() is adequate and simpler.

Toolbars

You can create a toolbar using CreateToolbarEx() but I'm not going to, so there. First thing you need to do is actually create the toolbar...

    hTool = CreateWindowEx(0, TOOLBARCLASSNAME, NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0,
        hwnd, (HMENU)IDC_MAIN_TOOL, GetModuleHandle(NULL), NULL);

That's simple enough, TOOLBARCLASSNAME is a constant defined by the common control headers. hwnd is the parent window, the one you want to put the toolbar in. IDC_MAIN_TOOL is an identifier that you can use later to get the HWND of the toolbar using GetDlgItem(), if you so desire.

     // Send the TB_BUTTONSTRUCTSIZE message, which is required for
     // backward compatibility.
     SendMessage(hTool, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);

This message is required to let the system figure out which version of the common controls library you are using. Since new versions add new stuff to the structure, by giving it the size it can figure out what behaviour you are expecting.

Toolbar buttons

Button bitmaps on basic toolbars come in two varieties, standard buttons that are provided by comctl32, and user defined buttons that you create yourself. NOTE: Buttons and bitmaps are added to toolbars seperately... first you add a list of images to use, and THEN you add a list of buttons, and telling it which button uses which image.

Adding Standard Buttons

Now that we have a toolbar created, we need to add some buttons to it. The most common bitmaps are available in the common control library itself, so we don't need to recreate them or add them to every exe that uses them.

First we declare a TBBUTTON and TBADDBITMAP

    TBBUTTON tbb[3];
    TBADDBITMAP tbab;
And then we add the standard bitmaps to the toolbar, using the imagelist predefined in the common control library...
    tbab.hInst = HINST_COMMCTRL;
    tbab.nID = IDB_STD_SMALL_COLOR;
    SendMessage(hTool, TB_ADDBITMAP, 0, (LPARAM)&tbab);
Now that we have our images loaded up, we can add some buttons that use them...
    ZeroMemory(tbb, sizeof(tbb));
    tbb[0].iBitmap = STD_FILENEW;
    tbb[0].fsState = TBSTATE_ENABLED;
    tbb[0].fsStyle = TBSTYLE_BUTTON;
    tbb[0].idCommand = ID_FILE_NEW;

    tbb[1].iBitmap = STD_FILEOPEN;
    tbb[1].fsState = TBSTATE_ENABLED;
    tbb[1].fsStyle = TBSTYLE_BUTTON;
    tbb[1].idCommand = ID_FILE_OPEN;

    tbb[2].iBitmap = STD_FILESAVE;
    tbb[2].fsState = TBSTATE_ENABLED;
    tbb[2].fsStyle = TBSTYLE_BUTTON;
    tbb[2].idCommand = ID_FILE_SAVEAS;

    SendMessage(hTool, TB_ADDBUTTONS, sizeof(tbb)/sizeof(TBBUTTON), (LPARAM)&tbb);
Here we've added a New, Open and Save As button using the standard images, which is always a good idea since people are used to seeing them and they know what they mean.

The indexes of each image in the imagelist are defined in the common control headers and are listed in MSDN.

We have assigned each button an ID (ID_FILE_NEW etc...) which is identical to the IDs of the equivalent menu items. These buttons will generate WM_COMMAND messages identical to the menu, so no extra processing is required! If we were adding a button for a command that didn't already have a menu item, we would simply pick a new ID for it and add a handler to WM_COMMAND.

If you're wondering what's up with the funky wParam I passed to TB_ADDBUTTONS it's doing a calculation of the number of buttons in the array tbb so that we don't need to hardcode a value. If I put in 3 instead it would still be correct, but as soon as I added another button I'd have to change it to 4 and in programming that's bad... you want one change to cause as few other changes as possible. For example if the sizeof(TBBUTTON) was 16 bytes (I made that up, it actually varies by platform) then since we have 3 buttons the sizeof(tbb) would be 16 * 3 or 48. Therefor 48/16 gives us the number of buttons, 3.

Status bars

Something often found in apps with toolbars are status bars, the little things at the bottom of the window that display information. They're pretty simple to use, just create...
    hStatus = CreateWindowEx(0, STATUSCLASSNAME, NULL,
        WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP, 0, 0, 0, 0,
        hwnd, (HMENU)IDC_MAIN_STATUS, GetModuleHandle(NULL), NULL);
And then (optionally) set the number of sections that you want. If you don't set any, it will simply have one section using the entire width of the bar, and you can set and retreive the text with SetWindowText() as with many other controls. For more than one part, you need to give the widths of each section, and then use SB_SETTEXT to set the text of each one.

To define the widths, we declare an array of ints, where each value is the width in pixels of a section. If you want one section to use up any remaining space, set it's width to -1.

    int statwidths[] = {100, -1};

    SendMessage(hStatus, SB_SETPARTS, sizeof(statwidths)/sizeof(int), (LPARAM)statwidths);
    SendMessage(hStatus, SB_SETTEXT, 0, (LPARAM)"Hi there :)");

The wParam again is our calculation of how many elements are in the array. Once we're done adding sections, we set the first one (index 0) to see it in action.

Proper Sizing

Unlike menus, tool and status bars are seperate controls that live inside the parent window's client area. Therefor if we just leave our WM_SIZE code from before, they are going to overlap with the edit control we added in the previous examples. This is a simple matter to correct... in WM_SIZE, we move the tool and status bars into position, and then subtract their heights and positions from the client area so that we can move our edit control to fill the remaining space...
    HWND hTool;
    RECT rcTool;
    int iToolHeight;

    HWND hStatus;
    RECT rcStatus;
    int iStatusHeight;

    HWND hEdit;
    int iEditHeight;
    RECT rcClient;

    // Size toolbar and get height

    hTool = GetDlgItem(hwnd, IDC_MAIN_TOOL);
    SendMessage(hTool, TB_AUTOSIZE, 0, 0);

    GetWindowRect(hTool, &rcTool);
    iToolHeight = rcTool.bottom - rcTool.top;

    // Size status bar and get height

    hStatus = GetDlgItem(hwnd, IDC_MAIN_STATUS);
    SendMessage(hStatus, WM_SIZE, 0, 0);

    GetWindowRect(hStatus, &rcStatus);
    iStatusHeight = rcStatus.bottom - rcStatus.top;

    // Calculate remaining height and size edit

    GetClientRect(hwnd, &rcClient);

    iEditHeight = rcClient.bottom - iToolHeight - iStatusHeight;

    hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
    SetWindowPos(hEdit, NULL, 0, iToolHeight, rcClient.right, iEditHeight, SWP_NOZORDER);

Unfortunately it's a somewhat long code snippet, but it's quite simple... toolbars will auto position themselves when sent the TB_AUTOSIZE message, and status bars will do the same if you send them WM_SIZE (the common control libraries are not known for consistancy).

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


App Part 4: Multiple Document Interface

MDI Overview

First a bit of background... Every window has a Client Area, this is where most programs draw images, place controls etc... the Client Area is not seperate from the window itself, it is simply a smaller specialised region of it. Sometimes a window can be all client area, and nothing else, sometimes the client area is smaller to make room for menus, titles, scrollbars, etc...

In MDI terms, your main window is called the Frame, this is probably the only window you would have in a SDI (Single Document Interface) program. In MDI there is an additional window, called the MDI Client Window which is a child of your Frame window. Unlike the Client Area it is a complete and seperate window all on it's own, it has a client area of it's own and probably a few pixels for a border. You never directly handle messages for the MDI Client, it is done by the pre-defined windows class "MDICLIENT". You can communicate with and manipulate the MDI Client and the windows it contains through messages.

When it comes to the windows which actually display your document or whatever your program displays, you send a message to the MDI Client to tell it to create a new window of the type you've specified. The new window is created as a child of the MDI Client, not of your Frame window. This new window is an MDI Child. The MDI Child is a child of the MDI Client, which in turn is a child of the MDI Frame (Getting dizzy yet?). To make matters worse, the MDI Child will probably have child windows of its own, for instance the edit control in the example program for this section.

You are responsable for writing two (or more) Window Procedures. One, just like always, for your main window(the Frame). And one more for the MDI Child. You may also have more than one type of Child, in which case, you'll want a seperate window procedure for each type.

If I've thoroughly confused you now talking about MDI Clients and things, this diagram may clear things up a little better:

Getting Started with MDI

MDI requires a few subtle changes throughout a program, so please read through this section carefully... chances are that if your MDI program doesn't work or has strange behaviour it's because you missed one of the alterations from a regular program.

MDI Client Window

Before we create our MDI window we need to make a change to the default message processing that goes on in our Window Procedure... since we're creating a Frame window that will host an MDI Client, we need to change the DefWindowProc() call to DefFrameProc() which adds specialized message handling for Frame Windows,
    default:
        return DefFrameProc(hwnd, g_hMDIClient, msg, wParam, lParam);
The next step is to create the MDI Client window itself, as a child of our frame window. We do this in WM_CREATE as usual...
    CLIENTCREATESTRUCT ccs;

    ccs.hWindowMenu  = GetSubMenu(GetMenu(hwnd), 2);
    ccs.idFirstChild = ID_MDI_FIRSTCHILD;

    g_hMDIClient = CreateWindowEx(WS_EX_CLIENTEDGE, "mdiclient", NULL,
        WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL | WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        hwnd, (HMENU)IDC_MAIN_MDI, GetModuleHandle(NULL), (LPVOID)&ccs);
The menu handle is the handle to the popup menu that the MDI client will add items to representing each window that is created, allowing the user to select the window they want to activate from the menu, we'll add functionality shortly to handle this case. In this example it's the 3rd popup (index 2) since I've added Edit and Window to the menu after File.

ccs.idFirstChild is a number to use as the first ID for the items the Client adds to the Window menu... you want this to be easily distinguishable from your own menu identifiers so you can handle your menu commands and pass the Window menu commands to DefFrameProc() for processing. In the example I specify an identifier defined as 50000, high enough that I know none of my menu command id's will be above it.

Now to get this menu to work properly we need to add some special handling to our WM_COMMAND handler:

    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
            case ID_FILE_EXIT:
                PostMessage(hwnd, WM_CLOSE, 0, 0);
            break;

            // ... handle other regular IDs ...

            // Handle MDI Window commands
            default:
            {
                if(LOWORD(wParam) >= ID_MDI_FIRSTCHILD)
                {
                    DefFrameProc(hwnd, g_hMDIClient, msg, wParam, lParam);
                }
                else
                {
                    HWND hChild = (HWND)SendMessage(g_hMDIClient, WM_MDIGETACTIVE,0,0);
                    if(hChild)
                    {
                        SendMessage(hChild, WM_COMMAND, wParam, lParam);
                    }
                }
            }
        }
    break;

I've added a default: case which will catch all commands that I didn't process directly and do a check to see if the value is greater than or equal to ID_MDI_FIRSTCHILD. If it is, then the user has clicked on one of the Window menu items and we send the message on to DefFrameProc() for processing.

If it isn't one of the Window IDs then I get the handle to the active child window and forward the message to it for processing. This allows you to delegate responsibility to the Child windows for performing certain actions, and allows different child windows to handle commands in different ways if so desired. In the example I only handle commands that are global to the program in the Frame window procedure, and send the commands which affect a certain document or child window on to the child window itself for processsing.

Since we're building on the last example, the code to size the MDI client is the same as the code to resize the edit control in the last example, that takes into account the size and position of the tool and status bars so they don't overlap the MDI client window.

We also need to modify our message loop a little...

    while(GetMessage(&Msg, NULL, 0, 0))
    {
        if (!TranslateMDISysAccel(g_hMDIClient, &Msg))
        {
            TranslateMessage(&Msg);
            DispatchMessage(&Msg);
        }
    }
We've added an extra step (TranslateMDISysAccel()), that checks for the pre-defined accelerator keys, Ctrl+F6 which swtiches to the next window, Ctrl+F4 which closes the Child and so on. If you don't add in this check you will annoy your users by not providing the standard behaviour they've gotten used to, or you'll have to implement it manually.

Child Window Class

In addition to the main window of the program (the Frame window) we need to create new window classes for each type of child window we want. For example you might have one to display text, and one to display a picture or graph. In this example we'll only be creating one child type, which will be just like the editor program in the previous examples.
BOOL SetUpMDIChildWindowClass(HINSTANCE hInstance)
{
    WNDCLASSEX wc;

    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = MDIChildWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_3DFACE+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szChildClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(0, "Could Not Register Child Window", "Oh Oh...",
            MB_ICONEXCLAMATION | MB_OK);
        return FALSE;
    }
    else
        return TRUE;
}
This is basically identical to registering our regular frame window, there are no particularly special flags here for use with MDI. We've set the menu as NULL, and the window procedure to point to the child window procedure which we will write next.

MDI Child Procedure

The window procecure for an MDI child is much like any other with a few small exceptions. First of all, default messages are passed to DefMDIChildProc() instead of DefWindowProc().

In this particular case, we also want to disable the Edit and Window menu's when they aren't needed (just because it's a nice thing to do), so we handle WM_MDIACTIVEATE and enable or disable them depending on if our window is getting activated or not. If you have multiple types of child window, this is where you could put code to completely change the menu or toolbar or make alterations to other aspects of the program to reflect the actions and commands that are specific to the type of window being activated.

To be even more complete, we can disable the Close and Save File menu items as well, since they aren't going to be any good with no windows to act on. I've disabled all these items by default in the resource so that I don't need to add extra code to do it when the application first starts up.

LRESULT CALLBACK MDIChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CREATE:
        {
            HFONT hfDefault;
            HWND hEdit;

            // Create Edit Control

            hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "",
                WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,
                0, 0, 100, 100, hwnd, (HMENU)IDC_CHILD_EDIT, GetModuleHandle(NULL), NULL);
            if(hEdit == NULL)
                MessageBox(hwnd, "Could not create edit box.", "Error", MB_OK | MB_ICONERROR);

            hfDefault = GetStockObject(DEFAULT_GUI_FONT);
            SendMessage(hEdit, WM_SETFONT, (WPARAM)hfDefault, MAKELPARAM(FALSE, 0));
        }
        break;
        case WM_MDIACTIVATE:
        {
            HMENU hMenu, hFileMenu;
            UINT EnableFlag;

            hMenu = GetMenu(g_hMainWindow);
            if(hwnd == (HWND)lParam)
            {      //being activated, enable the menus
                EnableFlag = MF_ENABLED;
            }
            else
            {                          //being de-activated, gray the menus
                EnableFlag = MF_GRAYED;
            }

            EnableMenuItem(hMenu, 1, MF_BYPOSITION | EnableFlag);
            EnableMenuItem(hMenu, 2, MF_BYPOSITION | EnableFlag);

            hFileMenu = GetSubMenu(hMenu, 0);
            EnableMenuItem(hFileMenu, ID_FILE_SAVEAS, MF_BYCOMMAND | EnableFlag);

            EnableMenuItem(hFileMenu, ID_FILE_CLOSE, MF_BYCOMMAND | EnableFlag);
            EnableMenuItem(hFileMenu, ID_FILE_CLOSEALL, MF_BYCOMMAND | EnableFlag);

            DrawMenuBar(g_hMainWindow);
        }
        break;
        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case ID_FILE_OPEN:
                    DoFileOpen(hwnd);
                break;
                case ID_FILE_SAVEAS:
                    DoFileSave(hwnd);
                break;
                case ID_EDIT_CUT:
                    SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_CUT, 0, 0);
                break;
                case ID_EDIT_COPY:
                    SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_COPY, 0, 0);
                break;
                case ID_EDIT_PASTE:
                    SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_PASTE, 0, 0);
                break;
            }
        break;
        case WM_SIZE:
        {
            HWND hEdit;
            RECT rcClient;

            // Calculate remaining height and size edit

            GetClientRect(hwnd, &rcClient);

            hEdit = GetDlgItem(hwnd, IDC_CHILD_EDIT);
            SetWindowPos(hEdit, NULL, 0, 0, rcClient.right, rcClient.bottom, SWP_NOZORDER);
        }
        return DefMDIChildProc(hwnd, msg, wParam, lParam);
        default:
            return DefMDIChildProc(hwnd, msg, wParam, lParam);

    }
    return 0;
}

I've implemented the File Open and Save as commands, the DoFileOpen() and DoFileSave() are nearly the same as in previous examples with the ID of the edit control changed, and additionally setting the title of the MDI Child to the filename.

The Edit commands are easy, because the edit control has built in support for them, we just tell it what to do.

Remember I mentioned that there are little things you need to remember or your application will behave strangely? Note that I've called DefMDIChildProc() at the end of WM_SIZE, this is important otherwise the system wont' have a chance to do it's own processing on the message. You can look up DefMDIChildProc() in MSDN for a list of the messages that it processes, and always be sure to pass them to it.

Creating and Destroying Windows

MDI Child windows are not created directly, isntead we send a WM_MDICREATE message to the client window telling it what kind of window we want by setting the members of an MDICREATESTRUCT. You can look up the various members of this struct in your documentation, they are fairly straight forward. The return value from the WM_MDICREATE message is the handle to the newly created window.
HWND CreateNewMDIChild(HWND hMDIClient)
{
    MDICREATESTRUCT mcs;
    HWND hChild;

    mcs.szTitle = "[Untitled]";
    mcs.szClass = g_szChildClassName;
    mcs.hOwner  = GetModuleHandle(NULL);
    mcs.x = mcs.cx = CW_USEDEFAULT;
    mcs.y = mcs.cy = CW_USEDEFAULT;
    mcs.style = MDIS_ALLCHILDSTYLES;

    hChild = (HWND)SendMessage(hMDIClient, WM_MDICREATE, 0, (LONG)&mcs);
    if(!hChild)
    {
        MessageBox(hMDIClient, "MDI Child creation failed.", "Oh Oh...",
            MB_ICONEXCLAMATION | MB_OK);
    }
    return hChild;
}

One member of MDICREATESTRUCT that I didn't use that can be quite usefull is the lParam member. This can be used to send any 32bit value (like a pointer) to the child you are creating in order to provide it with any custom information you choose. In the WM_CREATE handler for your child window, the lParam value for the WM_CREATE message will point to a CREATESTRUCT. the lpCreateParams member of that structure will point to the MDICREATESTRUCT you sent along with WM_MDICREATE. So in order to access the lParam value from the Child window you need to do something like this in the child window procedure...

    case WM_CREATE:
    {
        CREATESTRUCT* pCreateStruct;
        MDICREATESTRUCT* pMDICreateStruct;

        pCreateStruct = (CREATESTRUCT*)lParam;
        pMDICreateStruct = (MDICREATESTRUCT*)pCreateStruct->lpCreateParams;

        /*
        pMDICreateStruct now points to the same MDICREATESTRUCT that you
        sent along with the WM_MDICREATE message and you can use it
        to access the lParam.
        */
    }
    break;

If you don't want to bother with those two extra pointers you can access the lParam in one step with ((MDICREATESTRUCT*)((CREATESTRUCT*)lParam)->lpCreateParams)->lParam

Now we can implement the File commands on our menu in our Frame window procedure:

    case ID_FILE_NEW:
        CreateNewMDIChild(g_hMDIClient);
    break;
    case ID_FILE_OPEN:
    {
        HWND hChild = CreateNewMDIChild(g_hMDIClient);
        if(hChild)
        {
            DoFileOpen(hChild);
        }
    }
    break;
    case ID_FILE_CLOSE:
    {
        HWND hChild = (HWND)SendMessage(g_hMDIClient, WM_MDIGETACTIVE,0,0);
        if(hChild)
        {
            SendMessage(hChild, WM_CLOSE, 0, 0);
        }
    }
    break;

We can also provide some default MDI processing of window arrangment for our Window menu, since MDI supports this itself it's not much work.

    case ID_WINDOW_TILE:
        SendMessage(g_hMDIClient, WM_MDITILE, 0, 0);
    break;
    case ID_WINDOW_CASCADE:
        SendMessage(g_hMDIClient, WM_MDICASCADE, 0, 0);
    break;

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Bitmaps, Device Contexts and BitBlt

GDI

The really great thing about MS Windows is that unlike DOS, you don't need to know anything about what video hardware you are using to display graphics. Instead, windows provides an API called the Graphics Device Interface, or GDI. The GDI uses a set of generic graphics objects that can be used to draw to the screen, to memory, or even to printers.

Device Contexts

The GDI revolves around an object called the Device Context (DC), represented by the data type HDC (Handle to Device Context). An HDC is basically a handle to something you can draw on; it can represent the entire screen, an entire window, the client area of a window, a bitmap stored in memory, or a printer. The nice part is that you don't even need to know which one it refers to, you can use it basically the same way, which is especially handy for writing custom drawing functions which you can then use on any of these devices without changing it for each one.

An HDC like most GDI objects is opaque, meaning that you can't access it's data directly... but you can pass it to various GDI functions that will operate on it, either to draw something, get information about it, or change the object in some way.

For example, if you wanted to draw on a window, first you would retreive an HDC representing the window with GetDC(), then you could use any of the GDI functions that take an HDC like BitBlt() for drawing images, TextOut() for drawing text, LineTo() for lines and so on.

Bitmaps

Bitmaps can be loaded much like icons in earlier examples, there is LoadBitmap() for the most basic functionality of simply loading a bitmap resource, and LoadImage() can be used to load bitmaps from a *.bmp file just as it can for icons.

One of the quirks of GDI is that you can't draw to bitmap objects (HBITMAP type) directly. Remember that drawing operations are abstracted by Device Contexts, so in order to use these drawing functions on a bitmap, you need to create a Memory DC, and then select the HBITMAP into it with SelectObject(). The effect is that the "device" that the HDC refers to is the bitmap in memory, and when you operate on the HDC, the resulting graphic operations are applied to the bitmap. As I mentioned, this is actually a very conveiniant way of doing things, as you can write code that draws to an HDC and you can use it on a Window DC or a Memory DC without any checks or changes.

You do have the option of manipulating the bitmap data in memory yourself. You can do this with Device Independant Bitmaps (DIB), and you can even combine GDI and manual operations on the DIB. However for the time being, this is beyond the scope of the basic tutorial and for now we're just cover the simpler GDI operations on their own.

GDI Leaks

Once you're finished with an HDC, it's very important to release it (just how you do that depends on how you got it, which we'll talk about in a bit). GDI objects are limited in number. In versions of windows prior to Windows 95, they were not only incredably limited but also shared system wide, so that if one program used up too many, none of the rest would be able to draw anything! Fortunately this isn't the case any longer, and you could get away with using up quite a lot of resources in Windows 2000 or XP before anything too bad happened... but it's easy to forget to free GDI objects and they can quickly run your program out of GDI resources under Windows 9x. Theorhetically you shouldn't be able to drain the system of GDI resources in NT systems (NT/2K/XP) but it still happens in extreme cases, or if you hit the right bug on the nose.

If your program runs fine for a few minutes and then starts drawing strangely or not at all, it's a good sign that you're leaking GDI resources. HDCs aren't the only GDI objects you need to be careful about releasing, but generally it's ok to keep things like bitmaps and fonts around for the entire lifetime of your program, since it's much more efficient than reloading them each time you need them.

Also, an HDC can only contain one of each type of object (bitmap, font, pen...) at a time, and when you select a new one in it will return the last one. It's very important that you deal with this object properly. If you ignore it completely, it might be lost and pile up in memory as a GDI leak. When an HDC is created, it's also created with some default objects selected into it... it's a good idea to store these when they are returned to you, and then when you are completed drawing with the HDC select them back into it.

This is particularly important as you can't delete a GDI object while it is selected into an HDC. To clean up properly, you should reselect the default object back into the HDC releasing the object you had selected into it, which you can then DeleteObject() successfully. Trying to delete a bitmap you created while that bitmap is selected into an HDC will fail, and probably leak the bitmap if you assume it has been cleaned up from that point on.

Even Microsoft engineers get caught by this, there was a bug in one of the Windows screensavers and it turns out it was because the default bitmap wasn't getting replaced causing a leak and eventually the screensaver ran out of GDI resources. Be warned! It's an easy mistake to make.

Displaying Bitmaps

Ok, down to business. The simplest drawing operations on a window occur by handling WM_PAINT. When your window is first displayed, restored from being minimised, or uncovered from having another window on top of it, Windows sends the WM_PAINT message to the window to let it know that it needs to redraw it's contents. When you draw something on the screen it is NOT permanent, it's only there untill something else draws over it, and you will need to redraw it when that part of your window becomes visible to the user again.
HBITMAP g_hbmBall = NULL;

    case WM_CREATE:
        g_hbmBall = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BALL));
        if(g_hbmBall == NULL)
            MessageBox(hwnd, "Could not load IDB_BALL!", "Error", MB_OK | MB_ICONEXCLAMATION);
    break;
The first step is of course loading the bitmap, this is quite simple with a bitmap resource, there are no significant differences from loading other resource types. Then we can get down to drawing...
    case WM_PAINT:
    {
        BITMAP bm;
        PAINTSTRUCT ps;

        HDC hdc = BeginPaint(hwnd, &ps);

        HDC hdcMem = CreateCompatibleDC(hdc);
        HBITMAP hbmOld = SelectObject(hdcMem, g_hbmBall);

        GetObject(g_hbmBall, sizeof(bm), &bm);

        BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

        SelectObject(hdcMem, hbmOld);
        DeleteDC(hdcMem);

        EndPaint(hwnd, &ps);
    }
    break;

Getting the Window DC

To start off we declare a couple of variables we need. Notice that the first one is a BITMAP, not an HBITMAP. BITMAP is a struct that holds information about an HBITMAP which is the actual GDI object. We need a way to get the height and width of the HBITMAP so we use GetObject() which contrary to it's name doesn't really get an object, but rather information about an existing one. "GetObjectInfo" would have been a more appropriate label. GetObject() works for various GDI object types which it can distinguish based on the value of the second parameter, the size of the structure.

The PAINTSTRUCT is a structure that contains information about the window being painted and what exactly is going on with the paint message. For most simple tasks, you can simply ignore the information it contains, but it's required for the call to BeginPaint(). BeginPaint() as it's name suggests is designed specifically for handling the WM_PAINT message. When not handling a WM_PAINT message you would use GetDC() which we will see in the timer animation examples in a while... but in WM_PAINT, it's important to use BeginPaint() and EndPaint().

BeginPaint() returns us an HDC that represents the HWND that we pass to it, the one that WM_PAINT is being handled for. Any drawing operation we perform on this HDC will immediately display on the screen.

Setting up a Memory DC for the Bitmap

As I mention above, in order to draw on or with bitmaps, we need to create a DC in memory... the easiest way to do that here is to CreateCompatibleDC() with the one we already have. This gives us a Memory DC that is compatible with the color depth and display properties of the HDC for the window.

Now we call SelectObject() to select the bitmap into the DC being careful to store the default bitmap so that we can replace it later on and not leak GDI objects.

Drawing

Once we've gotten the dimentions of the bitmap filled into the BITMAP struct, we can call BitBlt() to copy the image from our Memory DC to the Window DC, thus displaying on the screen. As always, you can look up each parameter in MSDN, but in short they are: The destination, the position and size, the source and source position, and finally the Raster Operation (ROP code), which specifies how to do the copy. In this case, we want a simple exact copy of the source made, no fancy stuff.

BitBlt() is probably the all time happiest function in all of the Win32 API and is the staple diet of anyone learning to write games or other graphics applications in windows. It was probably the first API that I memorised all the parameters to.

Cleanup

At this point the bitmap should be on the screen, and we need to clean up after ourselves. The first thing to do is restore the Memory DC to the state it was when we got it, which means replacing our bitmap with the default one that we saved. Next we can delete it altogether with DeleteDC().

Finally we release the Window DC we got from BeginPaint() using EndPaint().

Destroying an HDC is a little confusing sometimes because there are at least 3 ways to do it depending on how you got it in the first place. Here's a list of the common methods of gaining an HDC, and how to release it when you're done.

  • GetDC() - ReleaseDC()
  • BeginPaint() - EndPaint()
  • CreateCompatibleDC() - DeleteDC()

And finally, at the termination of our program, we want to free any resources that we allocated. Technically speaking this isn't absolutely required, since modern Windows platforms are pretty good at freeing everything when your program exists, but it's always a good idea to keep track of your own objects because if get lazy and don't delete them they have a habit of getting loose. And no doubt, there are still bugs in windows especially older versions that won't clean up all of your GDI objects if you don't do a thorough job.

    case WM_DESTROY:
        DeleteObject(g_hbmBall);
        PostQuitMessage(0);
    break;

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Transparent Bitmaps

Transparency

Giving bitmaps the appearance of having transparent sections is quite simple, and involves the use of a black and white Mask image in addition to the colour image that we want to look transparent.

The following conditions need to be met for the effect to work correctly: First off, the colour image must be black in all areas that we want to display as transparent. And second, the mask image must be white in the areas we want transparent, and black elsewhere. The colour and mask images are displayed as the two left most images in the example picture on this page.

BitBlt operations

How does this get us transparency? First we BitBlt() the mask image using the SRCAND operation as the last parameter, and then on top of that we BitBlt() the colour image using the SRCPAINT operation. The result is that the areas we wanted transparent don't change on the destination HDC while the rest of the image is drawn as usual.

    SelectObject(hdcMem, g_hbmMask);
    BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCAND);

    SelectObject(hdcMem, g_hbmBall);
    BitBlt(hdc, 0, bm.bmHeight, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCPAINT);

Pretty simple eh? Fortunately it is, but one question remains... where does the mask come from? There are basically two ways to get the mask...

  • Make it yourself in whatever graphics program you made the colour bitmap in, and this is a reasonable solution if you are using a limited number of graphics in your program. This way you can just add the mask resource to your program and load it with LoadBitmap().
  • Generate it when your program runs, by selecting one colour in your original image to be your "transparent" colour, and create a mask that is white everywhere that colour exists, and black everywhere else.

Since the first one is nothing new, you should be able to do things that way yourself if you want to. The second way involves from BitBlt() trickery, and so I will show one way of accomplishing this.

Mask Creation

The simplest way to do it, would be to loop through every pixel on the colour image, check it's value and then set the corresponding pixel on the mask to black or white... SetPixel() is a very slow way to draw images however, and it's not really practical.

A much more efficient way involves using the way BitBlt() converts from colour images to black and white. If you BitBlt() (using SRCCOPY) from an HDC holding a colour image into an HDC holding a black and white image, it will check what colour is set as the Background Colour on the colour image, and set all of those pixels to White, any pixel that is not the background colour will end up Black.

This works perfectly to our advantage, since all we need to do is set the background colour to the colour we want transparent, and BitBlt() from the colour image to the mask image. Note that this only works with a mask bitmap that is monochrome (black and white)... that is bitmaps with a bit depth of 1 bit per pixel. If you try it with a colour image that only has black and white pixels, but the bitmap itself is greater than 1 bit (say 16 or 24 bit) then it won't work.

Remember the first condition for succesful masking above? It was that the colour image needs to be black everywhere we want transparent. Since the bitmap I used in this example already meets that condition it doesn't really need anything special done, but if you're going to use this code for another image that has a different colour that you want transparent (hot pink is a common choice) then we need to take a second step, and that is use the mask we just created to alter the original image, so that everywhere we want transparent is black. It's ok if other places are black too, because they aren't white on the mask, they won't end up transparent. We can accomplish this by BitBlt()ing from the new mask to the original colour image, using the SRCINVERT operation, which sets all the areas that are white in the mask to black in the colour image.

This is all a bit of a complex process, and so it's nice to have a handy utility function that does this all for us, and here it is:

HBITMAP CreateBitmapMask(HBITMAP hbmColour, COLORREF crTransparent)
{
    HDC hdcMem, hdcMem2;
    HBITMAP hbmMask;
    BITMAP bm;

    // Create monochrome (1 bit) mask bitmap.

    GetObject(hbmColour, sizeof(BITMAP), &bm);
    hbmMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);

    // Get some HDCs that are compatible with the display driver

    hdcMem = CreateCompatibleDC(0);
    hdcMem2 = CreateCompatibleDC(0);

    SelectBitmap(hdcMem, hbmColour);
    SelectBitmap(hdcMem2, hbmMask);

    // Set the background colour of the colour image to the colour
    // you want to be transparent.
    SetBkColor(hdcMem, crTransparent);

    // Copy the bits from the colour image to the B+W mask... everything
    // with the background colour ends up white while everythig else ends up
    // black...Just what we wanted.

    BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

    // Take our new mask and use it to turn the transparent colour in our
    // original colour image to black so the transparency effect will
    // work right.
    BitBlt(hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem2, 0, 0, SRCINVERT);

    // Clean up.

    DeleteDC(hdcMem);
    DeleteDC(hdcMem2);

    return hbmMask;
}
NOTE: This function call SelectObject() to temporarily select the colour bitmap we pass it into an HDC. A bitmap can't be selected into more than one HDC at a time, so make sure the bitmap isn't selected in to another HDC when you call this function or it will fail. Now that we have our handy dandy function, we can create a mask from the original picture as soon as we load it:
    case WM_CREATE:
        g_hbmBall = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BALL));
        if(g_hbmBall == NULL)
            MessageBox(hwnd, "Could not load IDB_BALL!", "Error", MB_OK | MB_ICONEXCLAMATION);

        g_hbmMask = CreateBitmapMask(g_hbmBall, RGB(0, 0, 0));
        if(g_hbmMask == NULL)
            MessageBox(hwnd, "Could not create mask!", "Error", MB_OK | MB_ICONEXCLAMATION);
    break;

The second parameter is of course the colour from the original image that we want to be transparent, in this case black.

How does all this work?

.. you may be asking. Well hopefully your experience with C or C++ means that you understand binary operations such as OR, XOR, AND, NOT and so on. I'm not going to explain this process completely, but I will try to show how I used it for this example. If my explanation isn't clear enough (which it's bound to not be), reading up on binary operations should help you understand it. Understanding it isn't critical for using it right now, and you can just get away with trusting that it works if you want.

SRCAND

The SRCAND raster operation, or ROP code for BitBlt() means to combine the bits using AND. That is: only bits that are set both in the source AND the destination get set in the final result. We use this with our mask to set to black all the pixels that will eventually have colour on them from the colour image. The mask image has black (which in binary is all 0's) where we want colour, and white (all 1's) where we want transparency. Any value combined with 0 using AND is 0, and therefor all the pixels that are black in the mask are set to 0 in the result and end up black as well. Any value that is combined with 1 using AND is left unaffected, so if it was 1 to begin with it stays 1, and if it was 0 to begin with it stays 0... therefor all the pixels that are white in our mask, are completely unaffected after the BitBlt() call. The result is the top right image in the example picture.

SRCPAINT

SRCPAINT uses the OR operation, so if either (or both) of the bits are set, then they will be set in the result. We use this on the colour image. When the black (transparent) part of our colour image is combined with the data on the destination using OR, the result is that the data is untouched, because any value combined with 0 using the OR operation is left unaffected.

However, the rest of our colour image isn't black, and if the destination also isn't black, then we get a combination of the source and destination colours, the result you can see in the second ball on the second row in the example picture. This is the whole reason for using the mask to set the pixels we want to colour to black first, so that when we use OR with the colour image, the coloured pixels don't get mixed up with whatever is underneath them.

SRCINVERT

This is the XOR operation used to set the transparent colour in our original image to black (if it isn't black already). Combining a black pixel from the mask with a non-background colour pixel in the destination leaves it untouched, while combining a white pixel from the mask (which remember we generated by setting a particular colour as the "background") with the background colour pixel on the destination cancels it out, and sets it to black.

This is all a little GDI mojo that depends on it's colour vs. monochrome handling, and it hurts my head to think about it too much, but it really makes sense... honest.

Example

The example code in the project bmp_two that goes along with this section contains the code for the example picture on this page. It consists of first drawing the mask and the colour image exactly as they are using SRCCOPY, then using each one alone with the SRCAND and SRCPAINT operations respectively, and finally combining them to produce the final product.

The background in this example is set to gray to make the transparency more obvious, as using these operations on a white or black background makes it hard to tell if they're actually working or not.

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Timers and Animation

Setting up

Before we get things animated, we need to set up a structure to store the position of the ball between updates. This struct will store the current position and size of the ball, as well as the delta values, how much we want it to move each frame.

Once we have the structure type declared, we also declare a global instance of the struct. This is ok since we only have one ball, if were were going to animate a bunch of them, you'd probably want to use an array or other container (such as a linked list in C++) to store them in a more convenient way.

const int BALL_MOVE_DELTA = 2;

typedef struct _BALLINFO
{
    int width;
    int height;
    int x;
    int y;

    int dx;
    int dy;
}BALLINFO;

BALLINFO g_ballInfo;
We've also defined a constant BALL_MOVE_DELTA which is how far we want the ball to move on each update. The reason we store deltas in the BALLINFO structure as well is that we want to be able to move the ball left or right and up and down independantly, BALL_MOVE_DELTA is just a handy name to give the value so we can change it later if we want.

Now we need to initialize this structure after we load our bitmaps:

    BITMAP bm;

    GetObject(g_hbmBall, sizeof(bm), &bm);

    ZeroMemory(&g_ballInfo, sizeof(g_ballInfo));
    g_ballInfo.width = bm.bmWidth;
    g_ballInfo.height = bm.bmHeight;

    g_ballInfo.dx = BALL_MOVE_DELTA;
    g_ballInfo.dy = BALL_MOVE_DELTA;

The ball starts off in the top left corner, moving to the right and down according to the dx and dy members of BALLINFO.

Setting the Timer

The easiest way to add a simple timer into a window program is with SetTimer(), it's not the best, and it's not recommended for real multimedia or full games, however it's good enough for simple animations like this. When you need something better take a look at timeSetEvent() in MSDN; it's more accurate.
const int ID_TIMER = 1;

    ret = SetTimer(hwnd, ID_TIMER, 50, NULL);
    if(ret == 0)
        MessageBox(hwnd, "Could not SetTimer()!", "Error", MB_OK | MB_ICONEXCLAMATION);
Here we've declared a timer id so that we can refer to it later (to kill it) and then set the timer in the WM_CREATE handler of our main window. Each time the timer elapses, it will send a WM_TIMER message to the window, and pass us back the ID in wParam. Since we only have one timer we don't need the ID, but it's useful if you set more than one timer and need to tell them apart.

We've set the timer to elapse every 50 milliseconds, which results in approximately 20 frames per second. Approximately because like I said, SetTimer() is a little inaccurate, but this isn't critical code, and a few milliseconds here or there won't kill us.

Animating in WM_TIMER

Now when we get WM_TIMER we want to calculate the new position for the ball and draw it's updated position.
    case WM_TIMER:
    {
        RECT rcClient;
        HDC hdc = GetDC(hwnd);

        GetClientRect(hwnd, &rcClient);

        UpdateBall(&rcClient);
        DrawBall(hdc, &rcClient);

        ReleaseDC(hwnd, hdc);
    }
    break;

I've put the code for updating and drawing the ball in their own functions. This is good practice, and it lets us draw the ball from either WM_TIMER or WM_PAINT without duplicating code, note that the method we use to get the HDC in each case is different, so it's best to leave this code in the message handlers and pass the result into the DrawBall() function.

void UpdateBall(RECT* prc)
{
    g_ballInfo.x += g_ballInfo.dx;
    g_ballInfo.y += g_ballInfo.dy;

    if(g_ballInfo.x < 0)
    {
        g_ballInfo.x = 0;
        g_ballInfo.dx = BALL_MOVE_DELTA;
    }
    else if(g_ballInfo.x + g_ballInfo.width > prc->right)
    {
        g_ballInfo.x = prc->right - g_ballInfo.width;
        g_ballInfo.dx = -BALL_MOVE_DELTA;
    }

    if(g_ballInfo.y < 0)
    {
        g_ballInfo.y = 0;
        g_ballInfo.dy = BALL_MOVE_DELTA;
    }
    else if(g_ballInfo.y + g_ballInfo.height > prc->bottom)
    {
        g_ballInfo.y = prc->bottom - g_ballInfo.height;
        g_ballInfo.dy = -BALL_MOVE_DELTA;
    }
}
All this does is some basic math, we add the delta value to the x position to move the ball. If the ball goes outside the client area, move it back in range and change the delta value to the opposite direction so that the ball "bounces" off the sides.
void DrawBall(HDC hdc, RECT* prc)
{
    HDC hdcBuffer = CreateCompatibleDC(hdc);
    HBITMAP hbmBuffer = CreateCompatibleBitmap(hdc, prc->right, prc->bottom);
    HBITMAP hbmOldBuffer = SelectObject(hdcBuffer, hbmBuffer);

    HDC hdcMem = CreateCompatibleDC(hdc);
    HBITMAP hbmOld = SelectObject(hdcMem, g_hbmMask);

    FillRect(hdcBuffer, prc, GetStockObject(WHITE_BRUSH));

    BitBlt(hdcBuffer, g_ballInfo.x, g_ballInfo.y, g_ballInfo.width, g_ballInfo.height, hdcMem, 0, 0, SRCAND);

    SelectObject(hdcMem, g_hbmBall);
    BitBlt(hdcBuffer, g_ballInfo.x, g_ballInfo.y, g_ballInfo.width, g_ballInfo.height, hdcMem, 0, 0, SRCPAINT);

    BitBlt(hdc, 0, 0, prc->right, prc->bottom, hdcBuffer, 0, 0, SRCCOPY);

    SelectObject(hdcMem, hbmOld);
    DeleteDC(hdcMem);

    SelectObject(hdcBuffer, hbmOldBuffer);
    DeleteDC(hdcBuffer);
    DeleteObject(hbmBuffer);
}
This is essentially the same drawing code as the past few examples, with the exception that it gets the position and dimentions of the ball from the BALLINFO structure. There is however one important difference...

Double Buffering

When doing your drawing directly to the HDC of the window, it's entirely possible that the screen will get updated before you're done... for example after you draw the mask and before you draw the colour image over top, the user might see a flicker of the back background before your program has a chance to draw over it in colour. The slower your computer and the more drawing operations that you do, the more flicker will be apparent and eventually it will look like a big jumbled mess.

This is terribly distracting, and we can solve it simply by doing all the drawing in memory first, and then copying the completed masterpiece to the screen in a single BitBlt() so that the screen is updated directly from the old image, to the complete new image with none of the individual operations visible.

To do this, we create a temporary HBITMAP in memory that is the exact size of the area we are going to draw to on the screen. We also need an HDC so that we can BitBlt() to the bitmap.

    HDC hdcBuffer = CreateCompatibleDC(hdc);
    HBITMAP hbmBuffer = CreateCompatibleBitmap(hdc, prc->right, prc->bottom);
    HBITMAP hbmOldBuffer = SelectObject(hdcBuffer, hbmBuffer);
Now that we have a place to draw to in memory, all of the drawing operations use hdcBuffer instead of hdc (the window) and the results are stored on the bitmap in memory untill we are complete. We can now copy the whole thing over to the window in one shot.
    BitBlt(hdc, 0, 0, prc->right, prc->bottom, hdcBuffer, 0, 0, SRCCOPY);
That's it, and we clean up our HDCs and HBITMAPs as usual.

Faster Double Buffering

In this example I am creating and destroying the bitmap used for double buffering each frame, I did this basically because I wanted to be able to size the window so it's easier to just always create a new buffer than to track when the window position changes and resize the buffer. It would be more efficient to create a global double buffer bitmap and either not allow the window to resize or only resize the bitmap when the window resized, instead of creating it and destroying it all the time. It's up to you to implement this if you want to optimize the drawing for a game or something.

Killing the Timer

When our window is destroyed, it's a good idea to release all resources we used, and in this case that includes the timer we set. To stop it, we simply call KillTimer() and pass in the ID that we used when we created it.
    KillTimer(hwnd, ID_TIMER);

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Text and Fonts

Loading Fonts

The Win32 GDI has some remarkable capabilites for dealing with vastly different typefaces, styles, languages and characters sets. One of the drawbacks of this is that dealing with fonts can look rather intimidating to the newcomer. CreateFont(), the primary API when it comes to fonts, has 14 parameters for specifying height, style, weight, family, and various other attributes.

Fortunately, it's not really has hard as it might appear, and a large portion of the work involved is taken care of my sensible default values. All but 2 of the parameters to CreateFont() can be set to 0 or NULL, and the system will simply use a default value giving you a plain ordinary font.

CreateFont() creates an HFONT, a handle to a Logical Font in memory. The data held by this handle can be retreived into a LOGFONT structure using GetObject() just as a BITMAP struct can be filled from an HBITMAP.

The members of the LOGFONT are identical to the parameters to CreateFont() and for convenience you can create a font directly from an existing LOGFONT structure using CreateFontIndirect(). This is very handy, since it makes it simple to create a new font from an existing font handle when you only want to alter certain aspects of it. Use GetObject() to fill a LOGFONT, alter the members that you wish, and create a new font with CreateFontIndirect().

    HFONT hf;
    HDC hdc;
    long lfHeight;

    hdc = GetDC(NULL);
    lfHeight = -MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72);
    ReleaseDC(NULL, hdc);

    hf = CreateFont(lfHeight, 0, 0, 0, 0, TRUE, 0, 0, 0, 0, 0, 0, 0, "Times New Roman");

    if(hf)
    {
        DeleteObject(g_hfFont);
        g_hfFont = hf;
    }
    else
    {
        MessageBox(hwnd, "Font creation failed!", "Error", MB_OK | MB_ICONEXCLAMATION);
    }

This is the code used to create the font in the example image. This is Times New Roman at 12 Point with the Italics style set. The italics flag is the 6th parameter to CreateFont() which you can see we have set to TRUE. The name of the font we want to use is the last parameter.

The one bit of trickery in this code is the value used for the size of the font, the lfHeight parameter to CreateFont(). Usually people are used to working with Point sizes, Size 10, Size 12, etc... when dealing with fonts. CreateFont() however doesn't accept point sizes, it wants Logical Units which are different on your screen than they are on your Printer, and even between Printers and screens.

The reason this situation exists is because the resolution of different devices is so vastly different... Printers can easily display 600 to 1200 pixels per inch, while a screen is lucky to get 200... if you used the same sized font on a printer as on a screen, you likely wouldn't even be able to see individual letters.

All we have to do is convert from the point size we want, into the appropriate logical size for the device. In this case the device is the screen, so we get the HDC to the screen, and get the number of logical pixels per inch using GetDeviceCaps() and slap this into the formula so generously provided in MSDN which uses MulDiv() to convert from our pointsize of 12 to the correct logical size that CreateFont() expects. We store this in lfHeight and pass it as the first parameter to CreateFont().

Default Fonts

When you first call GetDC() to get the HDC to your window, the default font that is selected into it is System, which to be honest isn't all that attractive. The simplest way to get a reasonable looking font to work with (without going through the CreateFont() hassle) is to call GetStockObject() and ask for the DEFAULT_GUI_FONT.

This is a system object and you can get it as many times as you want without leaking memory, and you can call DeleteObject() on it which won't do anything, which is good because now you don't need to keep track of whether your font is one from CreateFont() or GetStockObject() before trying to free it.

Drawing Text

Now that we have a handy-dandy font, how do we get some text on the screen? This is assuming that we don't just want to use an Edit or Static control.

Your basic options are TextOut() and DrawText(). TextOut() is simpler, but has less options and doesn't do word wrapping or alignment for you.

    char szSize[100];
    char szTitle[] = "These are the dimensions of your client area:";
    HFONT hfOld = SelectObject(hdc, hf);

    SetBkColor(hdc, g_rgbBackground);
    SetTextColor(hdc, g_rgbText);

    if(g_bOpaque)
    {
        SetBkMode(hdc, OPAQUE);
    }
    else
    {
        SetBkMode(hdc, TRANSPARENT);
    }

    DrawText(hdc, szTitle, -1, prc, DT_WORDBREAK);

    wsprintf(szSize, "{%d, %d, %d, %d}", prc->left, prc->top, prc->right, prc->bottom);
    DrawText(hdc, szSize, -1, prc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

    SelectObject(hdc, hfOld);

First thing we do is use SelectObject() to get the font we want to use into our HDC and ready for drawing. All future text operations will use this font untill another one is selected in.

Next we set the Text and Background colours. Setting the background colour doesn't actually make the whole background this colour, it only affects certain operations (text being one of them) that use the background colour to draw with. This is also dependant on the current Background Mode. If it is set to OPAQUE (the default) then any text drawn is filled in behing with the background colour. If it is set to TRANSPARENT then text is drawn without a background and whatever is behind will show through and in this case the background colour has no effect.

Now we actually draw the text using DrawText(), we pass in the HDC to use and the string to draw. The 3rd parameter is the length of the string, but we've passed -1 because DrawText() is smart enough that it will figure out how long the text is itself. In the 4th parameter we pass in prc, the pointer to the client RECT. DrawText() will draw inside this rectangle based on the other flags that you give it.

In the first call, we specify DT_WORDBREAK, which defaults to aligned to the top left, and will wrap the text it draws automatically at the edge of the rectangle... very useful.

For the second call, we're only printing a single line without wrapping, and we want it to be centered horizontally as well as vertically (which DrawText() will do only when drawing a single line).

Client Redraw

Just a note about the example program... when the WNDCLASS is registered I have set the CS_VREDRAW and CS_HREDRAW class styles. This causes the entire client area to be redrawn if the window is resized, whereas the default is to only redraw the parts that have changed. That looks really bad since the centered text moves around when you resize and it doesn't update like you'd expect.

Choosing Fonts

In general, any program that deals with fonts will want to let the user choose their own font, as well as the colour and style attribute to use when displaying it.

Like the common dialogs for getting open and save file names, there is a common dialog for choosing a font. This is, oddly enough, called ChooseFont() and it works with the CHOOSEFONT structure for you to set the defaults it should start with as well as returning the final result of the users selection.

HFONT g_hfFont = GetStockObject(DEFAULT_GUI_FONT);
COLORREF g_rgbText = RGB(0, 0, 0);

void DoSelectFont(HWND hwnd)
{
    CHOOSEFONT cf = {sizeof(CHOOSEFONT)};
    LOGFONT lf;

    GetObject(g_hfFont, sizeof(LOGFONT), &lf);

    cf.Flags = CF_EFFECTS | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
    cf.hwndOwner = hwnd;
    cf.lpLogFont = &lf;
    cf.rgbColors = g_rgbText;

    if(ChooseFont(&cf))
    {
        HFONT hf = CreateFontIndirect(&lf);
        if(hf)
        {
            g_hfFont = hf;
        }
        else
        {
            MessageBox(hwnd, "Font creation failed!", "Error", MB_OK | MB_ICONEXCLAMATION);
        }

        g_rgbText = cf.rgbColors;
    }
}

The hwnd in this call is simply the window you want to use as the parent for the font dialog.

The easiest way to use this dialog is in conjunction with an existing LOGFONT structure, which is most likely from whichever HFONT you are currently using. We set the lpLogFont member of the structure to point to the LOGFONT that we just filled with our current information and also added the CF_INITTOLOGFONTSTRUCT flag so that ChooseFont() knows to use this member. The flag CF_EFFECTS tells ChooseFont() to allow the user to select a colour, as well as Underline and Strikeout attributes.

Oddly enough, the Bold and Italics styles don't count as effects, they are considered part of the font itself and in fact some fonts only come in Bold or Italics. If you want to check or prevent the user from selecting a bold or italic font you can check the lfWeight and lfItalic members of the LOGFONT respectively, after the user has made their selection. You can then prompt the user to make another selection or something change the members before calling CreateFontIndirect().

The colour of a font is not associated with an HFONT, and therefor must be stored seperately, the rgbColors member of the CHOOSEFONT struct is used both to pass in the initial colour and retreive the new colour afterward.

CF_SCREENFONTS indicates that we want fonts designed to work on the screen, as opposed to fonts that are designed for printers. Some support both, some only one or the other. Depending on what you're going to be using the font for, this and many other flags can be found in MSDN to limit exactly which fonts you want the user to be able to select.

Choosing Colours

In order to allow the user to change just the colour of the font, or to let them pick a new colour for anything at all, there is the ChooseColor() common dialog. This is the code used to allow the user to select the background colour in the example program.
COLORREF g_rgbBackground = RGB(255, 255, 255);
COLORREF g_rgbCustom[16] = {0};

void DoSelectColour(HWND hwnd)
{
    CHOOSECOLOR cc = {sizeof(CHOOSECOLOR)};

    cc.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ANYCOLOR;
    cc.hwndOwner = hwnd;
    cc.rgbResult = g_rgbBackground;
    cc.lpCustColors = g_rgbCustom;

    if(ChooseColor(&cc))
    {
        g_rgbBackground = cc.rgbResult;
    }
}

This is fairly straightforward, again we're using the hwnd parameter as the parent to the dialog. The CC_RGBINIT parameter says to start off with the colour we pass in through the rgbResult member, which is also where we get the colour the user selected when the dialog closes.

The g_rgbCustom array of 16 COLORREFs is required to store any values the user decides to put into the custom colour table on the dialog. You could potentially store these values somewhere like the registry, otherwise they will simply be lost when your program is closed. This parameter is not optional.

Control Fonts

Something else you might want to do at some point is change the font on the controls on your dialog or window. This is usually the case when using CreateWindow() to create controls as we've done in previous examples. Controls like windows use System by default, so we used WM_SETFONT to set a new font handle (from GetStockObject()) for the control to use. You can use this method with fonts you create from CreateFont() as well. Simply pass the font handle as wParam and set lParam to TRUE to make the control redraw.

I've done this in previous examples, but it makes sense to mention it here because it's relevant and very short:

    SendDlgItemMessage(hwnd, IDC_OF_YOUR_CONTROL, WM_SETFONT, (WPARAM)hfFont, TRUE);

Where hfFont is of course the HFONT you want to use, and IDC_OF_YOUR_CONTROL is the ID of whichever control you want to change the font of.

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Win32 Tutorial - Recommended Books and References

Recommended Books and References

Books

If you expect anyone online to treat you with respect while you are learning, you NEED to get a good book to learn from. We're here to provide direction and explain things that need explaining, not to be your librarian or teach you step by step.

You can find more recommended books and links to buy at the #Winprog Store.

Programming Windows
by Charles Petzold. The book to get on Win32 API. If you want to write programs using just the API (which is what this tutorial covers), you need this book.
Programming Windows with MFC
by Jeff Prosise. If you want to venture into MFC (AFTER becoming fully accustomed to using the Win32 API), this is the book for you. If you don't like MFC but intend on getting a job doing windows developement, get this anyway, it's better to know than not.
Programming Applications for Windows
by Jeffrey Richter. Not for newbies, if you want to be up on managing processes and threads, dlls, windows memory management, exception handling, and hooking into the system, then this is the book for you.
Visual C++ Windows Shell Programming
by Dino Esposito. For anyone interested in the visual and user-friendly aspects of windows, this book covers writing extentions to the windows shell, working efficiently with files and drag and drop, customizing the taskbar and windows explorer, and numerous other tricks. Well worthwhile for anyone writing GUI apps in windows.
Network Programming for Microsoft Windows
Up to date information on network programming, including NetBIOS, mailslots and pipes, and of course the ever important windows sockets, complete with winsock2 and raw sockets. Also contains specific information on the various windows platforms including 2000 and CE.

Links

MSDN Online
This site has references for all imaginable Microsoft technologies, including full Win32 API and MFC documentation. If this didn't come with your compiler (ie. VC++) then the completely free online site will provide you with the required information. People will get really pissed off if you ask questions you could answer by doing a simple search on MSDN.
#winprog homepage
See FAQ and Store

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Win32 Tutorial - Free Borland C++ Command Line Tools

Free Borland C++ Command Line Tools

Obsolete

These tools are now so old and limited that I would not recommend using them at all.

Check out the Free VC++ 2008 tools available from Microsoft.

If you are perhaps a C++ historian, or alien archaeologist, feel free to keep reading...

Getting Them

Fortunately for anyone that wants to get into windows developement, Borland has offered its command line tools to the general public for FREE. Isn't that nice of them? There is no pretty IDE or resource editor, but beggers can't be choosers, and I'd have to say the compiler itself is of far better quality than either LCC-Win32 (which doesn't even do C++) or the various ports of other tools, gcc, mingw, cygwin, djgpp etc...

Read the readme to get yourself set up.

Borland C++ 5.5

What's extra spiffy is it even comes with a debugger! I don't use this, so I can't offer much help on it, but it's better than nothing. And if you're used to Turbo C++ from the DOS days, then this should be right up your ally.

For some reason Internet Explorer seems to have a problem with downloading this file, so if it clicking the link doesn't work, right click and Copy Shortcut, and then use your favourite FTP client to get it.

Turbo Debugger

Last but not least, a windows help file with full Win32 API reference. It's a few years old but still entirely accurate and much more convenient than MSDN online unless you need access to the most recent additions to the API (which if you're on this page, you don't). I use it regularly.

Win32 API Reference

Using Them

Basic commands

If you want to compile a single file program (simple_window.c for example), then you can use the following command:
bcc32 -tW simple_window.c
The -tW switch specifies a Win32 GUI application, instead of the default console application. You can compile multiple files into a single .exe by adding the other files to the end of this command.

Linking in Resources

This is a very frustrating issue for many users of the command line tools, and no wonder, since it seems borland tried to make it as hard as possible to link resources into your applications, the resource compiler brc32 no longer behaves as it did in earlier versions of the program where it would link the compiled resource into the .exe itself. When you run brc32 with no option to get the usage help, it still lists an option to turn .exe linking OFF, there simply appears to be no way to turn it ON.

I tried various combinations of command and options, but couldn't find any way to add a .res file to an .exe build with the above method. Which really sucks, cause the way I found to do it is a lot more complicated.

There is an easier way however...

BC++ now has an alternative method of including resources in a program by use of a #pragma (a non-standard preprocessor directive that compilers will ignore if they don't recognise it).

#pragma resource "app_name.res"
Placing this code in your main .c or .cpp file will cause the compiler to automatically link in the .res file that is generated from your .rc (.res is like an .obj file for resources).

Using the #pragma will allow you to compile programs nearly as simply as above, but you still need to compile the .rc file first using brc32. If you still want to use command line options as I did in the tutorial makefiles, read on...

The hard way...

These are the commands to use to compile the dlg_one example, including the resource.

bcc32 -c -tW dlg_one.c
ilink32 -aa -c -x -Gn dlg_one.obj c0w32.obj,dlg_one.exe,,import32.lib cw32.lib,,dlg_one.res
Nice eh? The -c option to bcc32 means compile only, don't link into an .exe. The -x -Gn options get rid of some extra files the linker creates that you probably don't need.

The real bugger with this is that since we are manually specifying the linker command, we need to include the default libraries and objs that the compiler would normally do for us. As you can see above, I've specified the appropriate files for a regular windows application.

To make things easier on yourself, it's best to do all this in a makefile. I've prepared a generic one that should work with all of the examples in the tutorial, and you should be able to adapt it to any of your own programs.

APP      = dlg_one
EXEFILE  = $(APP).exe
OBJFILES = $(APP).obj
RESFILES = $(APP).res
LIBFILES =
DEFFILE  =

.AUTODEPEND
BCC32   = bcc32
ILINK32 = ilink32
BRC32   = brc32

CFLAGS  = -c -tWM- -w -w-par -w-inl -W -a1 -Od
LFLAGS  = -aa -V4.0 -c -x -Gn
RFLAGS  = -X -R
STDOBJS = c0w32.obj
STDLIBS = import32.lib cw32.lib

$(EXEFILE) : $(OBJFILES) $(RESFILES)
   $(ILINK32) $(LFLAGS) $(OBJFILES) $(STDOBJS), $(EXEFILE), , \
      $(LIBFILES) $(STDLIBS), $(DEFFILE), $(RESFILES)

clean:
   del *.obj *.res *.tds *.map
You only need to modify the first 6 lines with the appropriate information.

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Win32 Tutorial - Solutions to Common Errors

Solutions to Common Errors

Error LNK2001: unresolved external symbol _main

An unresolved external occurs when some code has a call to a function in another module and the linker can't find that function in any of the modules or libraries that you are currently linking to.

In this specific case, it means one of two things. Either you are trying to write a Win32 GUI application (or non-console application) and accidently compiled it as a Console application... or you really are trying to compile a console application and didn't write or properly compile in a main() function.

Generally the first is the most common, if you specify Win32 Console as the project type in VC++ when you create your project you will get this error. You will also likely get it if you try to compile from the command line using BC++ but you neglect to specify the correct parameters to tell it to make a Win32 GUI application instead of a console app which is the default.

Fixing

If you're using VC++ re-create your project and select the Win32 Application project type (NOT "Console").

If you're using BC++ command line compiler, use -tW to specify a windows application.

Error C2440: cannot convert from 'void*' to 'HICON__ *' (or similar)

If you're compiling the code from this tutorial, it means that you are trying to compile it as C++ code. The code is written for the bcc32 and VC++ C compilers, and as such may not compile exactly the same under C++ since C++ has much stricter rules about converting types. C will just let it happen, C++ wants to you to make it explicit.

VC++ (and most compilers) will automatically compile a file with a .cpp extension as C++ code, and a file with a .c extension as C code. If you have added the tutorial code to a .cpp file, this is the most likely reason of getting this error.

If you're compiling code not from this tutorial, I can't guarantee that it's correct and therefor it may actually be an error that needs resolving. You'll have to use your own judgement to determine if it's safe to cast the value and remove the error, or if you are actually trying to make a variable be something it's not.

Fixing

If you want to use C, simply rename your file from .cpp to .c. Otherwise, simply add a cast, all of the code in the tutorial will work without any other changes when compiled as C++.

For example, in C this will work:

    HBITMAP hbmOldBuffer = SelectObject(hdcBuffer, hbmBuffer);

But in C++ requires a cast:

    HBITMAP hbmOldBuffer = (HBITMAP)SelectObject(hdcBuffer, hbmBuffer);

Fatal error RC1015: cannot open include file 'afxres.h'.

Oddly enough, VC++ adds afxres.h to resource files even when you aren't using an MFC project, and yet the file may only be installed if you install MFC. This perticular file isn't actually required, so to fix the error you can edit the .rc file in notepad and replace both occurances of "afxres.h" with "winres.h" (note that there should be two of them, and you need to change both).

Error LNK2001: unresolved external symbol InitCommonControls

You aren't linking to comctl32.lib which this API is defined in. This library is not included by default so you will either need to add it to the libraries on your command line, or add it in your VC++ project settings on the Link tab.

Dialog does not display when certain controls are added

Controls such as the ListView, TreeView, Hotkey, Progress Bar, and others are classified as Common Controls, as they were added to windows in comctl32.dll and were not available prior to Windows 95. Controls such as BUTTON, EDIT, LISTBOX, etc... while no doubt being common, are not "Common Controls" and I generally refer to them as "Standard Controls".

If you add a Common Control to a dialog and it fails to display, you most likely failed to call InitCommonControls() before running your dialog, or perhaps at all. The best place to call it is first thing in WinMain(). Calling it in WM_INITDIALOG is too late, since the dialog will fail before it reaches this point and it will never get called.

Some people and documentation may tell you that InitCommonControls() is deprecated and you should use InitCommonControlsEx(). Feel free to do this if you want, InitCommonControls() is just simpler and there's nothing wrong with using it.

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Win32 Tutorial - Why you should learn the API before MFC

Why you should learn the API before MFC

The Controversy

Too many people come on to IRC and ask "What is better, MFC or API?" and too many people are willing to say "MFC sucks" or "API sucks" either because of traumatic events involving one or the other in early childhood, or because everyone else is saying it.

The standard arguments are:

  • API is too hard
  • MFC is too confusing
  • API is too much code
  • MFC is bloated
  • API doesn't have wizards
  • MFC is badly designed
  • API isn't Object Oriented
  • MFC kicked my dog
  • API stole my girlfriend
And so on...

My Answer

My opinion, although by no means the only one, is that you should use the right framework for the right job.

First of all a clarification on what the API and MFC are. API is a generic term meaning Application Programming Interface, however in the context of Windows programming, it means specifically the Windows API, which is the lowest level of interaction between applications and the windows operating system. Drivers of course have even lower levels, and different sets of function calls to work with, but for the vast majority of windows development this is not an issue. MFC is a Class Library, it's a bunch of C++ classes that have been written to reduce the amount of work it takes to do certain things with the API. It also introduces an (arguably) Object Oriented framework into the application that you can either take advantage of or ignore, which is what most beginners do since the framework isn't really aimed at writing MP3 players, IRC clients or games.

Every program, whether it is written with MFC, Delphi, Visual Basic, perl, or any other wacked out language or framework you can think of, is eventually built upon the API. In many cases this interaction is hidden, so you don't deal directly with the API, the runtime and support libraries do it for you. Some people ask, "MFC can do Blah Blah Blah, can the API?" The answer is that MFC can only do what the API can do, because it's built on top of it. However doing things yourself with the API may take considerably more code than using the pre-written MFC classes.

So what is the right framework? For starters, for people that are just learning to program, I strongly believe that you should work with the API untill you are comfortable with the way windows applications work and you understand all of the basic mechanics behind things like the message loop, GDI, controls, and maybe even multithreading and sockets. This way you will understand the fundamental building blocks of all windows applications, and can apply this common knowledge to MFC, Visual Basic, or whatever other framework you choose to work with later. It's also important because these other frameworks don't support everything that the API does, simply because it does a whole lot and they can't necessarily support all of the arcane little things that most people won't use. So when you finally do need to use them you need to add it yourself, you can't rely on the framework to do it for you and if you don't understand the API this could be quite the chore.

But isn't MFC easier? In a certain sense it's easier in that many common tasks are done for you, thus reducing the amount of code that you need to actually type. However, less code does not mean "easier" when you don't understand the code you DO need to write, or how all of the code that is there to support you actually works. Generally beginners who use the wizards to start there applications have no idea what most of the generated code does, and spend a great deal of time trying to figure out where to add things, or what changes to make to acheive a certain result. If you start your programs from scratch, either in the API or with MFC, then you know where everything is because you put it there, and you will only use features that you understand.

Another important factor is that most people that are learing the Win32 API for the first time don't already have a strong base in C++. To try and comprehend windows programming with MFC and learn C++ at the same time can be a monumental task. Although it's not impossible, it will take you considerably longer to become productive than if you already knew either C++ or the API.

So basically...

What it comes down to is that I think you should learn the API untill you feel comfortable with it, and then try out MFC. If it seems like it's making sense to you and saving you time, then by all means use it.

However, and this is important... if you work with MFC without understanding the API and then ask for help with something, and the answer you get is stated using the api (such as "Use the HDC provided in the WM_CTLCOLORSTATIC message") and you say "huh?" because you don't know how to translate an API subject into MFC on your own, then you are in trouble and people will get frustrated with you for not learning what you need to know before you try and use MFC.

I personally prefer to work with the API, it just suits me better, but if I were to write a database frontend, or a host for a set of ActiveX controls I would seriously consider using MFC, as it would eliminate a lot of code that I would need to reinvent otherwise.

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Win32 Tutorial - Resource file notes

Resource file notes

Argh!

The one thing I really hated when I switched my primary development environment from Borland C++ to MS Visual C++ was the way VC++ handles resource scripts (.rc files).

In BC++ was free to control the layout and content of the .rc files, and when using the resource editor, only the things that I specifically changed in the editor got changed in the resource file. Much to my dismay, the VC++ resource editor will completely rewrite your .rc file, and possibly destroy or ignore any changes that you personally make.

This was terribly frustrating at first, but I basically learned to deal with it and it's not SO bad after a while, since in general I don't write any amount of my resources by hand, but reserve that for minor changes that perhaps I can't quite accomplish in the editor.

Compatibility

One small challange for this tutorial was to make the resource files compile properly under VC++ and BC++ without changes. In the original tutorial I used the Borland naming convention for the resource header, which was project_name.rh. However by default in VC++ this header is ALWAYS called resource.h, so for simplicity I've adopted this for the current tutorial revision, as it doesn't impact BC++ at all.

For the curious, it is possible to change the name of the resource that VC++ uses by editing the .rc file manually and changing the name in two places, once where it is #included, and second where it is contained in a TEXTINCLUDE resource.

The next problem is that by default VC++ requires the file afxres.h to be included in it's .rc files, whereas BC++ has all the necessary preprocessor macros defined automatically and requires no such include. Another dumb thing about this is that afxres.h is only installed when you insall MFC which not everyone does, even when you are creating an API application which only requires winres.h which is always installed.

Since I work in VC++ and use it's resource editor I've solved this problem by slightly altering each .rc file that is generated to include the following:

#ifndef __BORLANDC__
#include "winres.h"
#endif
Which under default circumstances would usually read:
#include "afxres.h"
For those of you that are using VC++ you can find the option to change this text within the IDE under "View > Resource Includes". There is generally no need to ever use this in normal practice, it's simply a way I used to work around the problem of making things work with BC++ and VC++.

To those of you using BC++, I'm sorry about the extra mess in the .rc files that are generate by the VC++ editor, but it shouldn't interfere with anything.

Compiling resources under BC++

Try as I might I couldn't find a simple way to compile a program with BC++ that included RC files, and ultimately had to settle on the non-optimal configuration that you will find in the makefiles included with the source for this tutorial. You can find the notes for the BC++ compiler in Free Borland C++ Command Line Tools.

Copyright © 1998-2013, Brook Miles (tut...@winprog.org). All rights reserved.


Catch22 | Win32 Software & Programming Tutorials

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Latest News

HexEdit 2.0.6

New release of HexEdit:

  • File saving bug fixes
  • Base64 and hex-dump import bug fixes
  • Fixed-offset addressing when resizing hex & ascii panes
  • Read more about HexEdit 2.0.6

HexEdit 2.0.4

A new version of HexEdit has been released - version 2.0.4. This version addressed a lot of the bugs and issues that were present in the earlier beta versions. HexEdit should be fairly stable and usable now!

www.catch22.net/software/hexedit

This new version also coincides with the release of the HexEdit sourcecode under MIT licence. Visit the sourcecode repository on github for more information:

https://github.com/strobejb/HexEdit

  • Read more about HexEdit 2.0.4

7Calc released

7Calc is a Windows 7 desktop gadget. It is the spiritual successor to Supercalc.

7Calc has been released under the MIT licence. Full sourcecode is available on github http://github.com/strobejb/supercalc

Visit the 7Calc homepage.

  • Read more about 7Calc released

HexEdit Open Sourced

HexEdit is finally seeing the light of day as a fully open-sourced project. I have a few changes to make in order to tidy up the source-code and make it presentable. Expected release date is very soon now.

  • Read more about HexEdit Open Sourced

Welcome to Catch22!

Welcome to the new-look Catch22!

  • Read more about Welcome to Catch22!
more

Featured Software

HexEdit 2.0

Hexadecimal file editor for Windows

WinSpy++

Window style editor

Win32 Tutorials

Neatpad

Design & Implementation of a Win32 Text Editor

Powered by Drupal


Contact | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Contact

Catch22 Productions is the name I have created to publish my personal projects. Catch22 is a non-profit making company. My real name is James Brown, and I am a professional software engineer specializing in win32 graphics and application programming.

Please email james@catch22.net if you would like to contact me. Include the text '[catch22]' in the subject line to make sure you get through my spam-filters :-)

Alternatively you can use the Contact form.

In 2006 and 2007 I was awarded MVP status by Microsoft, as a Windows SDK MVP

MVP for Windows SDK
2006 - 2007

I have created this site as a place to make publically available any software or programming related articles that I have written. The sole purpose of this site is to provide quality software for free download to any member of the internet community.

All software that you find on this site has been written without the aid of programming libraries such as MFC. Consequently, you will find that the software programs are much smaller in size than any equivalent program, and run faster and slicker as a result. I have spent alot of time ensuring that my software is bug-free and intuitive to use.

I hope you enjoy the Catch22 experience.

Powered by Drupal


Software | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Software

HexEdit 2.0

HexEdit is a hexadecimal file editor for Microsoft Windows. HexEdit allows the user to view and edit any type of file, no matter what format it is saved in.

With a maximum file size limit of 16Exabytes (17,179,869,184 gigabytes!!), HexEdit can cut, copy, paste, insert and delete any amount of data with no decrease in performance with larger files. With unlimited multi-level undo and redo capability, no matter how large the file, HexEdit brings a new level of control over file editing.

  • Read more about HexEdit 2.0

7Calc

New!

7Calc is a Desktop Gadget expression calculator for Windows 7. It is the spiritual successor to the original Supecalc program. 7Calc is implemented as a Desktop Gadget, which means that it can be docked to the side of your desktop.

The calculator has can be toggled between small layout, and a larger stretched layout.

Programmer Mode

7Calc allows full C-style expressions and uses integer maths to evaluate expressions, providing the same results that would be produced by a native C++ program.

  • Read more about 7Calc

Matrix Screensaver

The Matrix ScreenSaver is a small, fast and elegant Windows version of the green "Matrix" cypher-code seen in the films. With this version you can even add messages which are decoded displayed in realtime. With version 3.0, the screen saver now supports colourised matrix fields.

  • Read more about Matrix Screensaver

WinSpy++ 1.7

WinSpy++ is a handy programmer's utility which can be used to select and view the properties of any window in the system. WinSpy is based around the Spy++ utility that ships with Microsoft Visual Studio.

WinSpy++ can obtain the following window properties:

  • Read more about WinSpy++ 1.7

Shed

Shed is a single player card game for all 32bit versions of Windows. It is based on the popular student card-game of "Shit-head", which I believe is sometimes known as "Shit-boots" in America. It gets its name due to the profanity that is usually voiced when someone wins the game.

This version of the game allows you to re-create those chaotic games of Shed by playing against a computer opponent. I didn't think there'd be much use in a two-player or multi-player version, as this game is best played with a real deck of cards.

  • Read more about Shed

HexEdit

HexEdit is a hexadecimal file editor for Microsoft Windows. HexEdit allows the user to view and edit any type of file, no matter what format it is saved in.

With a maximum file size limit of 4Gb, HexEdit can cut, copy, paste, insert and delete any amount of data with no decrease in performance with larger files. With unlimited multi-level undo and redo capability, no matter how large the file, HexEdit brings a new level of control over file editing.

  • Read more about HexEdit

Supercalc

Supercalc is an expression calculator for Windows. An expression calculator gives the user the ability to type an equation exactly as it is to be evaluated. This allows for much more intuitive use of the calculator. Instead of the cumbersome buttons associated with a traditonal windows calculator, you simply type what you want.

  • Read more about Supercalc
Powered by Drupal


Sourcecode | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Sourcecode

Presented here are a collection of sourcecode snippets and mini-projects. All downloads are plain Win32 C and C++, and compile under Microsoft Visual Studio 6.0.

Code Snippets

A collection of C and C++ programming snippets organised into categories of functionality.

  • C/C++ Programming Snippets

Win32 Sourcecode projects

Sourcecode downloads which implement full applications or utilities

Global Titlebar Hook
25 Oct 2005
Insert buttons into window titlebars on a global level (system-wide). Handles XP Themes if available. Requires NT/2000/XP.
WinCap3
30 Mar 2005
Capture hidden and obscured windows - proof of concept demo application. Windows XP only - will not work under Win95/98/ME. Might work under NT,2000 but untested. Click here and here for old versions.
ReportView
6 Feb 2005
Force Report-style view in all Open and Save common dialogs.
Directory Tree
2 Feb 2005
Display a directory hierarchy using ASCII graphics (ala the "tree" DOS command).
Process Tree
4 Feb 2005
Display a process hierarchy like SysInternal's ProcessExplorer.
SetRes
Control multiple display resolutions and colour depths with this useful tool.

Win32 Sourcecode snippets

A collection of useful functions which I've accumulated over the years.

AttachDesktop
Attach to a specified desktop
RegHelper
2 Feb 2005
Registry utilities which mimic the GetPrivateProfile_xxx APIs
EnableDebug
4 Feb 2005
Enable SE_DEBUG_NAME and other system privileges (useful for opening system processes)

C++ Socket Library
6 Feb 2005
C++ class wrappers around the winsock library - makes socket programming much easier!

Trace macro
6 Feb 2005
"printf" style wrapper around the OutputDebugString API

DragFile
7 Feb 2005
Filename Drag+Drop the Windows 3.1 way
Alpha-blended windows
17 Jan 2002
Simple functions to provide alpha-blended window support.
Styles
6 Feb 2005
Helper functions to set window and dialog item styles, using SetWindowLong.
ForceSetForegroundWindow
15 Aug 2003
SetForegroundWindow replacement (that works!)
EnableDialogTheme
20 Jan 2002
Enable XP themes on dialog boxes
GetVersionString
6 Feb 2005
Howto retrieve version strings (e.g. File description) from an executable's version resources.

Windows NT sourcecode

The following sourcecode snippets are specific to the Windows NT family of operating systems - Windows NT, 2000, XP and 2003 Server. Many of the downloads are heavily influenced by USENET threads, in particular postings by Gary Nebbett.

SetConsolePalette
2 Oct 2005
Modify the Win32 Console's palette dynamically at runtime.
CreateToken
Create an access token for local user accounts without knowing the password - requires SeCreateTokenPrivilege level. You also require a custom ntdll.h if you don't already have one. Gary Nebbet original author.
QuerySystemInfo
4 Feb 2005
Wrapper around the ZwQuerySystemInformation native system service.
UsernameToSid
4 Feb 2005
Convert a username to SID
KMem
26 Mar 2004
Read kernel memory from usermode (kmemcpy function). Gary Nebbet original author.
InjectThread
2003
CreateRemoteThread replacement, can inject into an already-running thread. View the example usage.
BreakPoint
8 Feb 2005
Hardware breakpoint and single-step debugging routines.

Windows NT Device Driver snippets

Windows NT kernel-mode snippets.

IsrSpinlock
28 Oct 2003
Howto acquire a spinlock at any IRQL level - implemented as a custom spinlock
InterlockedExecute
6 Jun 2004
Acquire exclusive access to all CPUs and execute a single function with full system lock. Please send feedback on this technique if you find it useful!

22 Jan 2012

Powered by Drupal


Tutorials | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Tutorials

Presented here are a collection of programming tutorials for Windows. The tutorials cover various Windows programming topics, but most concentrate on the user-interface aspect of programming. All tutorials and source downloads are in the C programming language. No external libraries such as MFC are required to build the source code presented in these tutorials.

Tips
Win32 Tips & Tricks A page presenting many useful tips for Windows programming | 15 May 2001
Visual C++ Tips & Tricks Lots of useful tips for using Visual C++ | 26 Mar 2005
Win32 Tips & Tricks - Part 2 More useful tips for Win32 programming | 14 May 2006
C & C++ Tricks | 22 Jan 2012
User Interface
Insert buttons into an edit control How to insert a button into an edit control | 20 May 2001
Masked Edit Input How to filter specific keys-strokes from a standard edit control | 21 May 2001
Introduction to Printing How to print from a Windows application. | 22 May 2001
Fixed-width Font Enumeration How to enumerate all fixed-width fonts in a system along with their sizes | 22 May 2001
Customize date-time controls Improve the apearance of the date-time control | 23 Aug 2001
Drive-list control A drop-down list displaying drive names and icons | 25 Aug 2001
Directory list control A directory-picker list box | 26 Aug 2001
Custom Titlebar How to insert buttons into a window's caption area | 1 Oct 2001
URL Control A simple HTTP URL control | 1 Oct 2001
Custom Controls How to create a custom win32 control | 20 Nov 2001
Flicker-free Drawing Techniques to eliminate flicker from your applications | 20 Nov 2001
Custom Scrollbars Introducing the Cool Scrollbar library, for customizing the appearance of scrollbars | 5 Jan 2002
Custom MessageBox How to customize the standard MessageBox dialog | 30 Jun 2002
System Image List How to access the System Image List (AKA the Shell Icon Cache) | 2 Jul 2002
Custom Combobox How to customize the appearance of a combobox. | 4 Jan 2004
64bit Scrollbars How to create 64bit scroll ranges using standard Win32 scrollbars | 2 Sep 2005
Cards
Using cards.dll API How to access the cards.dll API | 22 May 2001
Cards.dll direct bitmap access Accessing the cards.dll bitmaps as resources | 1 Oct 2001
CardLib Introducing CardLib, a card-game programming library | 4 Dec 2001
CardLib Reference CardLib Programmer's Quick-Reference | 4 Dec 2001
Docking
Splitter Windows An example of a splitter-bar used to separate two windows | 22 May 2001
Docking Toolbars - Part 1 How to implement floating toolbars | 20 Nov 2001
Docking Toolbars - Part 2 How to implement docking toolbars | 30 Jun 2002
System
Memory Techniques - Part 1 Memory management techniques for large file editors | 20 Aug 2001
Memory Techniques - Part 2 Memory management techniques for large file editors | 30 Aug 2001
Self-deleting Executables Techniques which allow an executable file to delete itself whilst running | 1 Oct 2001
Reducing Executable Size A tutorial covering a variety of techniques for creating tiny executables. | 1 Oct 2001
Undocumented CreateProcess Undocumented and hard-to-find information regarding the CreateProcess API call | 6 Jan 2004
OLE
OLE Drag and Drop | 15 Feb 2004
Drag and Drop Introduction Introducing the OLE and COM technologies | 15 Feb 2004
Implementing IDataObject Standalone implementation of the IDataObject interface | 15 Jun 2004
OLE Data Transfers All about OLE data types, storage mediums and data format | 15 Jun 2004
Enumerating FORMATETC Standalone implementation of the IEnumFormatEtc interface | 16 Jun 2004
Drop Target Implementing the IDropTarget interface | 17 Jun 2004
Drop Source Implementing the IDropSource interface | 17 Jun 2004
Neatpad
Design & Implementation of a Win32 Text Editor Neatpad Text Editor | 28 Jan 2005
Neatpad Overview Introduction to the Win32 Text Editor series | 29 Jan 2005
Loading a text file Loading and displaying a text file | 18 Feb 2005
Scrollbars & Scrolling Adding scrollbars and scrolling to the TextView | 20 Feb 2005
Enhanced Drawing & Painting Improving the text display of TextView | 19 Apr 2005
Mouse Selection & Highlighting Mouse input, window focus and selection highlighting | 29 May 2005
Scrolling with the Mouse Mouse selection and scrolling | 30 May 2005
Margins and Long Lines Selection margin, line numbers and long-line highlighting | 7 Aug 2005
Introduction to Unicode An introduction to Unicode and character sets | 4 Dec 2005
Unicode Text Processing Unicode file support in Neatpad | 5 Dec 2005
Transparent Text Overview of the issues involved in rendering text selections | 25 Feb 2006
Introduction to Uniscribe Introducing Uniscribe | 27 Feb 2006
Uniscribe Mysteries Looking at ScriptItemize and ScriptLayout | 1 Mar 2006
More Uniscribe Mysteries Looking at ScriptShape and ScriptPlace | 2 Mar 2006
Integrating UspLib Migrating Neatpad to the UspLib engin | 6 Mar 2006
Drawing styled text with Uniscribe Drawing styled text with ScriptTextOut | 6 Mar 2006
Keyboard Navigation Keyboard navigation with Uniscribe | 14 May 2006
Piece Chains Editing text with Piece Chains | 12 Sep 2006
Unicode Text Editing Editing complex script | 28 Nov 2006
Kernel
Introduction to Device Drivers Kernel 101 - writing a simple Windows NT device driver | 25 Mar 2005
Remote Debugging using VMWare How to debug a device-driver using WinDbg and VMWare | 28 Mar 2005
Powered by Drupal


Win32 Tips & Tricks | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Win32 Tips & Tricks

A page presenting many useful tips for Windows programming

Introduction

This section presents a few win32 programming tips that I have picked up whilst learning to program Windows. Please select a topic from the list below.

  1. Filling areas with a solid colour
  2. Dragging a window by its client area
  3. Get the mouse position at any time
  4. Automatically link in a library from a source-level command
  5. Calculate the point size of a font
  6. Prevent a window from being resized
  7. Change the colours of an Edit control
  8. Center a window relative to its parent
  9. Prevent a Rebar's text labels from flickering
  10. Create non-rectangular windows
  11. Update the text in a status bar
  12. Display the customize dialog for a toolbar
  13. Resize a window without having to move it as well
  14. Give a Rebar control a double-gripper
  15. Calculate the size of a menu bar
  16. Share data between multiple processes
  17. Detect when the mouse has left a window
  18. Undocumented flags for GetDCEx
  19. Generate messages during compile time
  20. Undocumented Visual C stuff

Filling areas with a solid colour

The ExtTextOut API call is the fastest and simplest way to fill a rectangular area with a solid colour. Most people are aware of the FillRect API call. The disadvantage of this function is that it requires the user to supply a handle to a brush, which also means that you must look after this brush yourself. PatBlt is another fast way to fill an area with a brush, but has the same inconveniences as FillRect.

By using the ETO_OPAQUE flag in the call to ExtTextOut, and supplying a zero-length string to display, you can quickly and easily fill a rectangular area using the current background colour.

?
void PaintRect(HDC hdc, RECT *rect, COLORREF colour)
{
    COLORREF oldcr = SetBkColor(hdc, colour);
    ExtTextOut(hdc, 0, 0, ETO_OPAQUE, rect, "", 0, 0);
    SetBkColor(hdc, oldcr);
}

Dragging a window by its client area

The simplest way to drag a window by its client area (or drag a window if it has no title bar), is to handle the WM_NCHITTEST message that is sent to every window when a mouse event occurs. By returning a value of HTCAPTION from the WM_NCHITTEST message handler, you can fool windows into thinking that the mouse is over a caption bar, and the window can be dragged around with the mouse. It is best to only allow dragging by the client area of a window - otherwise, the window borders would become unusable. To achieve this, call the default window procedure, and check if its return value is HTCLIENT - if it is, then return HTCAPTION instead. Otherwise, just let the default behaviour take place.

?
UINT uHitTest;
...
case WM_NCHITTEST:
    uHitTest = DefWindowProc(hwnd, WM_NCHITTEST, wParam, lParam);
    if(uHitTest == HTCLIENT)
        return HTCAPTION;
    else
        return uHitTest;

Get the mouse position at any time

The current mouse position can always be retrieved with a call to GetCursorPos. However, the API call GetMessagePos returns the mouse position at the time when the last message was posted.

Automatically include a library from a source file (Visual C)

Including the following command in any source file will include a library search record in the resulting object file, which when it comes to link time, will ensure that the specified file will always be included.

?
#pragma comment( lib, "filename.lib" );

Calculate the point size of a font

The LOGFONT structure specifies the a fontsize in logical units, which is fairly useless in alot of cases. Alot of the time "points" are useful for displaying the size of a font in a user-interface. Use the following formula to convert from logical units to points, when using the MM_TEXT mapping mode.

?
int pointsize = MulDiv(logheight, 72, GetDeviceCaps(hdc, LOGPIXELSY));

To convert from points back to logical device coordinates, use

?
int logheight = -MulDiv(pointsize, GetDeviceCaps(hdc, LOGPIXELSY), 72);

Prevent a window from being resized

There are three ways to prevent a window from being resized.

1. Make sure the window does not have the WS_THICKFRAME style set, as this allows the user to resize the window.2. Handle the WM_GETMINMAXINFO message.3. Handle the WM_SIZING message.

Change the colours of an EDIT control

The best way to change the colours of an edit control is to handle the WM_CTLCOLOREDIT message in the parent window of the edit control. When you receive this message, you will have the device context in wParam. You can use SetTextColor and SetBkColor on this device context to set the colours. Lastly, you must return a handle to a brush (which you must assume ownership of), which is used to paint the edit control's background.

?
case WM_CTLCOLOREDIT:
    hdc = (HDC)wParam;
    SetTextColor(hdc, RGB(255,0,0));            // red
    SetBkColor(hdc, RGB(255,255,0));            // yellow
    return GetSysColorBrush(COLOR_3DHILIGHT);   // hilight colour

Center a window relative to its parent

Here's a quick and easy method to center any window relative to its parent window.

?
BOOL CenterWindow(HWND hwnd)
{
    HWND hwndParent;
    RECT rect, rectP;
    int width, height;
    int screenwidth, screenheight;
    int x, y;
    //make the window relative to its parent
    hwndParent = GetParent(hwnd);

    GetWindowRect(hwnd, &&rect);
    GetWindowRect(hwndParent, &rectP);

    width  = rect.right  - rect.left;
    height = rect.bottom - rect.top;

    x = ((rectP.right-rectP.left) -  width) / 2 + rectP.left;
    y = ((rectP.bottom-rectP.top) - height) / 2 + rectP.top;
    screenwidth  = GetSystemMetrics(SM_CXSCREEN);
    screenheight = GetSystemMetrics(SM_CYSCREEN);
    //make sure that the dialog box never moves outside of//the screen
    if(x < 0) x = 0;
    if(y < 0) y = 0;
    if(x + width  > screenwidth)  x = screenwidth  - width;
    if(y + height > screenheight) y = screenheight - height;
    MoveWindow(hwnd, x, y, width, height, FALSE);
        return TRUE;
}

Prevent a Rebar's text labels from flickering

A rebar control is usually contained within the top portion of a main window. Whenever the main window's size changes, the rebar must also be resized to fit. The obvious way to do this is to handle WM_SIZE, and use MoveWindow(0, 0, width, rebar_height) to size the rebar exactly. However, this causes the text labels on a rebar control to flicker. To prevent this from happening, size the rebar control to the cover total size of the main window's client area. The rebar has some special sizing logic inside it which stretches the rebar across its parent's width, but keeps its height constant, even though you told it to be stretched vertically as well. Believe it or not, this works:

In the main window's WM_SIZE handler:

?
case WM_SIZE:
    MoveWindow(hwndRebar, 0, 0, LOWORD(lParam), HIWORD(lParam));
    ...

Create non-rectangular windows

The SetWindowRgn API can be used to give any window a non-rectangular shape. A region must be created, using any of the Region functions, such as CreateRectRgn or CreateEllipticRgn. The region coordinates must be window-relative.

?
HRGN hrgn;
...
hrgn = CreateEllipticRgn(0, 0, 100, 100);
SetWindowRgn(hwnd, hrgn, TRUE);

Update the text in a status bar

Here's a useful function to set the text of any status bar pane using a printf-like call

?
#include <stdarg.h>
/* style can be SBT_NOBORDERS etc */
void SetStatusBarText(HWND hwndSB, int part, unsigned style, char *fmt, ...)
{
    char tmpbuf[128];
    va_list argp;
    va_start(argp, fmt);
    _vsnprintf(tmpbuf, sizeof(tmpbuf), fmt, argp);
    va_end(argp);
    //cannot use PostMessage, as the panel type is not set correctly
    SendMessage(hwndSB, SB_SETTEXT, (WPARAM)part | style, (LPARAM)(LPSTR)tmpbuf);
}

Display the Customize dialog for a toolbar

The standard windows customization dialog for toolbars can be displayed by sending the toolbar the TB_CUSTOMIZE message.

?
SendMessage(hwndToolbar, TB_CUSTOMIZE, 0, 0);

You must remember to handle the TBN_QUERYDELETE and TBN_QUERYINSERT notifications, and return non-zero for both of these. Without them, the customize dialog will appear very briefly and then vanish.

Resize a window without having to move it

The easiest way to resize or move a window is to use the MoveWindow API. There is another function though, SetWindowPos, which can be used just to resize a window, and to keep it in the same place, by specifying the appropriate flags.

?
void SizeWindow(HWND hwnd, int width, int height)
{
    SetWindowPos(hwnd, NULL, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
}

Give a Rebar control a double gripper

Does anyone else prefer the rebar controls that Internet Explorer 3 had? The ones with the double-grippers? There is an easy way to give the newer rebar controls (single-grippers) a new look, by using the custom draw feature that all common controls support.

You must create the rebar's bands with the RBBS_GRIPPERALWAYS style (to display the single vertical grip bar). You must also set each rebar band's header size to around 10 pixels to allow extra room for the addtional gripper.

?
REBARBANDINFO rbBand;
...
rbBand.cbSize   = sizeof(REBARBANDINFO);
rbBand.fMask    = RBBIM_STYLE | RBBIM_HEADERSIZE /* include the others here */;
rbBand.fStyle   = RBBS_NOVERT | RBBS_CHILDEDGE | RBBS_GRIPPERALWAYS;
rbBand.cxHeader = 10;

Handle the WM_NOTIFY message in the rebar control's parent window. When you receive a NM_CUSTOMDRAW notification for your rebar, use the following function to draw the extra gripper:

?
LRESULT RebarCustomDraw(NMCUSTOMDRAW *lpcd)
{
    RECT rect;
    if(lpcd->dwDrawStage == CDDS_PREPAINT)
    {
        return CDRF_NOTIFYPOSTPAINT;
    }
    else if(lpcd->dwDrawStage == CDDS_POSTPAINT && lpcd->dwItemSpec != 0)
    {
        SetRect(&rect, 5, 2, 8, 38);
        DrawEdge(lpcd->hdc, &rect, BDR_RAISEDINNER, BF_RECT|BF_LEFT|BF_RIGHT);
        return 0;
    }
    return CDRF_DODEFAULT;
}

Calculate the size of a menu bar

It is straight-forward to calculate the size of a menu bar, or even a drop-down menu. You can use the GetMenuItemRect function (available in all 32bit versions of Windows) to work out the size of each item, and then use a loop to add all of the individual items together to work out the size of the whole menu.

?
BOOL GetMenuRect(HWND hwnd, HMENU hmenu, RECT *pmenurect)
{
    RECT rect;
    int i;
    SetRect(pmenurect, 0, 0, 0, 0);
    for(i = 0; i < GetMenuItemCount(hmenu); i++)
    {
        GetMenuItemRect(hwnd, hmenu, i, &rect);
        UnionRect(pmenurect, pmenurect, &rect);
    }
    return TRUE;
}

Share data between multiple processes (Windows NT)

The simplest way to share data between multiple instances of the same program is to create a new section in the executable. You must use a some form of protected access to the variable to prevent threading problems.

?
// Instruct compiler to put g_SharedVariable in its own section,
// so it can be shared amongst multiple instances
#pragma data_seg("Shared")
LONG g_SharedVariable = -1;
#pragma data_seg()
#pragma comment(linker, "/section:Shared,rws")

Detect when the mouse has left a window

There are four ways to detect when the mouse has left a window

1. Use the TrackMouseEvent API and the WM_MOUSELEAVE message (Win98/NT4+)

2. By using SetCapture API. When the window first receives a WM_MOUSEMOVE message, set the mouse capture. When the mouse leaves the window, Windows will send the window one last WM_MOUSEMOVE (the coordinates will be outside the window's client area). You can use this fact to detect when the mouse has left a window.

3 . By using a Timer. When the mouse enters a window, set a timer going with a small interval (10ms, say). When the timer expires, check if the mouse is still in the window. If it is, then let the timer keep going. Otherwise, the mouse has left the window, and the timer can be stopped.

4 . By using a mouse hook. When the mouse enters a window, install a mouse hook to monitor all mouse events. By checking for WM_MOUSEMOVE messages, you can check when the mouse has left a window and remove the hook appropriately.

Undocumented flags for GetDCEx

(Curtesy of Feng Yuan) GetDCEx can be used to retrieve the device context for a window during processing of WM_NCPAINT. The documentation states that this is achieved by using

?
GetDCEx(hwnd, hrgn, DCX_WINDOW | DCX_INTERSECTRGN);

However, this call never works, because there is an undocumented flag to include which is not mentioned anywhere.

?
GetDCEx(hwnd, hrgn, DCX_WINDOW | DCX_INTERSECTRGN | 0x10000);

Generate messages during compile time

It is possible to produce messages in the compilation window when a certain line of source code is compiled. This can be very useful, as you can leave reminders or warnings that a certain section of code may need reviewing. Use the following #pragma statement to create a message to yourself whenever the source file is compiled:

?
#pragma MESSAGE(Add error checking here later);

This is the macro itself. Place this in a header file and include it in any source file that you want to include this message capability for.

?
#define chMSG(x)   #x
#define chMSG2(x)  chMSG(x)
#define MESSAGE(desc) message(__FILE__ "(" chMSG2(__LINE__) ") : message : " #desc)

Undocumented Visual C stuff

You can display x number of elements in the watch window, by specifying a number after the variable name.e.g. plist, 15 will display 15 elements of the array pointed to by plist (assuming plist is a pointer).

15 May 2001

Posted in Tutorials

win32
Powered by Drupal


Visual C++ Tips & Tricks | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Visual C++ Tips & Tricks

Lots of useful tips for using Visual C++

This section presents a collection of Visual C++ 6.0 tips which are often hard to find information about. All of the tips are useful in their own way, and many will work exactly as-is in Visual Studio .NET also, so take a moment to browse through the collection.

1. Debug build works, Release build doesn't ??

This is the single most common posting made to programming forums and usenet. The answer is simple - fix the bug in your code!!! The reason you are experiencing problems is usually due to uninitialized variables in functions or classes. Problems like this are present in both Debug and Release builds, but typically only manifest themselves in the release binary because the debug build is alot more tolerant to errors.

2. Fix Intellisense problems

This is the most frustrating "feature" of Visual Studio - when intellisense stops working. Simply exit Visual Studio and browse to your project directory - then delete the .ncb file for your project. When you restart Visual Studio the problem will be fixed.

3. Auto-indent current selection

Press Alt+F8 on any highlighted text in the code-editor, and the code will be automatically adjusted so that each line is at the correct indentation level. Selecting an entire function body properly indents each line within the function.

4. Define custom keywords

Visual C++ has the ability to highlight user-defined keywords in your code. The colour of these user-defined keywords is easily adjusted using the Options dialog, but adding user-defined keywords is not documented.

Simply create a file called "usertype.dat" in your C:\Program Files\Microsoft Visual Studio\Common\MSDev98\Bin directory (the same directory that contains MSDEV.EXE). The contents of this file is a series of custom keywords, each one on a separate line. These keywords do not need to contain the built-in types such as int, char etc - only additional keywords that you want highlighted.

Here is the usertype.dat I use on my development machine. This same tip applies to Visual Studio.NET - look in the C:\Program Files\Visual Studio.NET\Common7\IDE directory (where DEVENV.EXE lives) and place your usertype.dat file there.

5. Define custom colours in the IDE

The Visual C++ IDE only allows you to select from 16 different colours. If you are fussy about the these colours then you can modify the base 16 colours from the following registry location:

?
HKCU\Software\Microsoft\Devstudio\6.0\Format\Source Window\

Inside this key there are a series of binary data items, one for each user-definable entity in the source window - such as comments, keywords, strings and normal text. Each value is made up using the same format. The first four bytes are the foreground colour, in RGBA format (3 bytes for RGB and 1 byte for padding). The background colour follows immediately afterwards using the same format:

?
[ RR GG BB xx ][ RR GG BB xx ]
   Foreground     Background

You can use the Registry Editor to Export and Import your colour settings from machine-to-machine.

6. Column-based selection

The Visual studio editor lets you select text in a line-by-line manner. However, holding the ALT key down whilst making a selection with the mouse causes columns of text to be selected rather than rows (lines).s

7. View structure members

Position the cusor next to the same of a structure variable and press CTRL+SPACE. The structure members popup window will appear.

8. View function parameters

Position the cursor on/after the name of a function and press CTRL+SHIFT+SPACE. The function-parameters popup window will appear.

9. Display Disassembly

Whilst debugging a project, press ALT+8 to switch to assembler mode.

10. Find Definition of any identifier

Right-click the mouse on any variable, function, structure or macro. A popup menu will appear - select "Goto Definition Of". Usually an error box will appear stating that you need to rebuild your project with browse information. Click YES, and wait for the project to rebuild. Now when you "Goto Definition Of", Visual C++ will open the appropriate source / header file and jump to the line containing the definition of the item you just selected.

11. Bookmark lines of text

You can bookmark lines of text for further reference, using the "Mark" commands. Press Ctrl+F2 to mark/unmark a line of text. Press F2 to skip between marked positions within each source-file.

The list of current bookmarks can be displayed by the Edit -> Bookmarks dialog or by pressing ALT+F2.

12. Callgraphs

Position the cursor over any function name and hit ALT+F12. If your project was built with browse-information, a dialog will appear asking what you want to do. Select CallGraph to view a hierarchy of all sub-functions which are called by your function. This is a very useful feature often overlooked in Visual Studio.

13. Navigate around your source quickly

Hold down Control whilst using the Arrow Keys to make the cursor skip between words, rather than moving character-by-character.

14. Find corresponding brackets

Position the cursor over any "{", "[", "(", "}", "]" or ")" bracket and hit CTRL+"]". The cursor will move to the corresponding opening/closing bracket of that expression.

15. Show/Hide whitespace

Press CTRL+SHIFT+8 to show / hide whitespace markers.

16. Open header files

Right-click any filename in your source-code (i.e. in a string expression or #include line) and click "Open Document". Visual Studio will open the corresponding file in the IDE.

17. Installing Platform SDK

The simplest method to install a new SDK or DDK is to install the entire package to a new location on your hard-disk (rather than install over the old one). I always recommend storing SDKs and MSDN help libraries in a "simple" directory location so it is easy to browse to using the command-prompt. i.e.

C:\MSVS\SDK2004\ C:\MSVS\MSDN2004\

When you want to integrate this new SDK into Visual C++, bring up the Tools->Options dialog box, and select the "Directories" tab. There is a drop-down list which lets you entire directories for Include files (*.h) and Library files (*.lib). Enter a new directory location for the following paths and move them to the top of their respective lists:

C:\MSVS\SDK2004\Include C:\MSVS\SDK2004\Lib

When Visual C++ compiles your project, it first searches the specified directories for include and library files. By putting the new directories at the top of the list it searches those first, before resorting to the "default" locations.

18. Preserve Keyboard and Layout settings

Visual Studio's keyboard and layout settings are stored in the following locations:

?
HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\Keyboard
HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\Layout

Use REGEDIT to Export these registry locations to an external file, then simply Import the settings when you need them on any new machine.

19. Using MAP files

You can see exactly what functions and global variables have been compiled into your project by looking at the corresponding MAP file in your /Debug or /Release directory. To enable this feature, open the Project->Settings dialog and select the "Linker" tab. Make sure the "Generate Map File" checkbox is selected.

20. Generate assembly-language output

You can see the full assembly language output of your project (complete with corresponding source-line and op-code output). Open the Project->Settings dialog and select the "C/C++" tab. Select the "Listing Files" option from the drop-down list and then select what type of output you want. The resulting file is generated in your /Debug or /Release directory.

21. Tiny Executables!

Generate instantly smaller executables by performing the following steps:

  1. Open the Project Settings dialog from the Project menu.
  2. Select the C/C++ tab
  3. Select Code Generation from the Category combo-box.
  4. In the Use run-time library drop-down-list, select:

    Debug Multithreaded DLL for Debug buildsMultithreaded DLL for Release builds

Now add the following line to the top of your main source file and you will have instantly smaller exes:

?
#pragma comment(linker, "/OPT:NOWIN98")

22. Breakpoints Dialog

It is very easy to miss the Breakpoints dialog from the Edit menu (what a strange place to put it!).

However some very powerful debugging can be achieved. Set breakpoints when specific variables change in value, when a Window procedure is called with a certain window-message, or when other conditions are met.

23. Edit Executable Resources

Visual Studio is a fully functional binary resource editor. Simply open an executable or dll using the File->Open dialog, but before pressing OK, select the "Resources" option in the "OpenAs" drop-down-list. You can use this feature to edit menus, dialogs, strings and bitmaps in regular executables and dlls such as notepad, paint, wordpad etc.

24. Display Workspace window

Press Alt+0 (Alt+zero) at any time to bring up the Project Workspace window.

25. Debug Release Builds

It is possible (and usually desirable) to generate debug information for your release builds. This is advantageous because you can now debug problems that only manifest themselves in a release-build environment. You can do this in such a way that the debug information is not stored inside the executable (thus bloating it up) - instead, it is stored in a separate .pdb (program database) file. The only entry in the executable is a small string which directs the debugger to your program database, so you can distribute the executable without this sensitive information.

Select "Link" from the Project->Settings dialog, and then select "Debug" from the Category drop-down list. Make sure that the "Debug Info" checkbox is selected, and then enable "COFF Format".

Now for any source-file that you want debugging information generated for, select that file (or your entire project) in the Project->Settings dialog, and then make sure that the "Program Database" option is enabled in the C/C++ General-settings tab.

26. Warning Level 4

Catch more errors in your project by enabling "Warning Level 4" in the C/C++ General settings tab.

27. Console and Window applications

You can switch between Console and Win32 applications at any time by editing the Linker options manually in the "Project Options" edit-field under the Project->Settings->Link dialog. Edit the /SUBSYSTEM option to switch between console and gui builds. i.e.

/SUBSYSTEM:CONSOLE

/SUBSYSTEM:WINDOWS

28. Generate Browse Information

Select Generate Browse Info under the C/C++ project settings tab, for any source-files that you want to be included. This is very useful for searching for function definitions and declarations - just right-click a function / variable and select "Goto Definition Of..." and the IDE will take you straight there.

29. Visual Studio Debugger - Watch Window

There are many useful commands available in the watch window, which can alter the way variables and error values are displayed by the debugger.

Command | Description
eax | Displays the return value of a function after it returns.
@err | Display the current Win32 GetLastError value
string,su | Display the specified string as Unicode.
value,hr | Treat the specified value as a HRESULT and display it's string representation
value,wm | Decode the specified value as a Windows Message.
value,x | Display value in Hexadecimal
array,23 | Display exactly 23 elements of the specified array
address,mb | Display the specified address/variable as a memory-block hex-dump.

30. Avoid stepping into CRT functions

The reason the Visual Studio debugger steps into CRT functions (i.e. into new/delete) when you don't want it to, is because you installed the source-code to the CRT/MFC libraries. Either move the source-code to a different location, or prevent the Visual Studio debugger from stepping into specific functions (whilst pressing F11) by performing the following steps:

  1. Open AUTOEXP.DAT (from the COMMON\MSDEV98\BIN\ directory)
  2. Add a section called [ExecutionControl]
  3. Add a line in this section with the following format:

    functionName=NoStepInto className::*=NoStepInto

  4. Restart the IDE for the changes to take effect.

Of course as soon as you accidently step into a function that you didn't want to be in, simply press SHIFT+F11 to step out again.

26 Mar 2005

Posted in Tutorials

win32
Powered by Drupal


Win32 Tips & Tricks - Part 2 | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Win32 Tips & Tricks - Part 2

More useful tips for Win32 programming

Continuing on from the last part, here are more Win32 Tips and Tricks for you to browse.

XP Themes

Common-control manifests (which enable the XP themes) can be added to an executable in one of two ways:

Firstly, the manifest can be saved inside a file in the same directory as your executable. It must also have the same filename, with the text '.manifest' appended to the end:

?
AppName.exe
AppName.exe.manifest

The manifest is just a text file (in XML) and should look like this:

?
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity
        version="1.0.0.0"
        processorArchitecture="X86"
        name="CompanyName.ProductName.AppName"
        type="win32"
    />
    <description>Program Description</description>
    <dependency>
      <dependentAssembly>
        <assemblyIdentity
            type="win32"
            name="Microsoft.Windows.Common-Controls"
            version="6.0.0.0"
            processorArchitecture="X86"
            publicKeyToken="6595b64144ccf1df"
            language="*"
        />
      </dependentAssembly>
    </dependency>
  </assembly>
</xml>

You should change the <assemblyIdentity name="MyProg"> value to the same name as your executable.

The second method of adding the manifest is to embed it directly into the executable as a resource:

  1. Add a Custom Resource to your application with resource-type name "24"
  2. Change the resource-id to "1"
  3. Embed the manifest directly into the resource as binary data.

Safe Subclassing

Use the following function to safely sublass a window when you don't know if it is a Unicode/Ansi window or not:

?
WNDPROC SafeSubclassWindow(HWND hwnd, WNDPROC NewProc)
{
    if(IsWindowUnicode(hwnd))
        return (WNDPROC)SetWindowLongPtrW(hwnd, GWL_WNDPROC, (LONG_PTR)NewProc);
    else
        return (WNDPROC)SetWindowLongPtrA(hwnd, GWL_WNDPROC, (LONG_PTR)NewProc);
}

Your sublass procedure must now call the old window-procedure by making use of the following function, which calls the Unicode or Ansi version of CallWindowProc as appropriate:

?
LRESULT SafeCallWndProc(WNDPROC OldProc, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if(IsWindowUnicode(hwnd))
        return CallWindowProcW(OldProc, hwnd, msg, wParam, lParam);
    else
        return CallWindowProcA(OldProc, hwnd, msg, wParam, lParam);
}

Shell Dialog Fonts

Under Windows XP and 2000 a new shell fontname was introduced - "MS Shell Dlg". This is not a real font - instead it is a registry value which maps to whatever the current user-interface font is (usually Tahoma/Segoi UI etc). To use this new font for your dialogs, you must edit your resource.rc file manually and change the name to "MS Shell Dlg". You must also add the DS_SHELLFONT style to the dialog.

Note that there is a trick to getting this to work correctly. The dialog-template resource must a DIALOGEX resource type rather than the older DIALOG. You can add an extended dialog-style to the resource (such as WS_EX_CONTROLPARENT) to cause this change, because otherwise Visual Studio will think that it is an older DIALOG and will erase your work once you make any changes to the template.

Lastly if your dialog is going to be part of a property-sheet, then all panes must be DIALOGEX's otherwise the font will not work.

SHBrowseForFolder

It is possible to force SHBrowseForFolder start at a specific directory by using the callback function facility:

?
BOOL BrowseForPath(HWND hwndParent, TCHAR *szPath)
{
    BROWSEINFO  shbi = { 0 };
    ITEMIDLIST *pidl;
    BOOL success = FALSE;
    IMalloc    *pMalloc;

    shbi.hwndOwner  = hwndParent;
    shbi.ulFlags    = BIF_NONEWFOLDERBUTTON | BIF_RETURNONLYFSDIRS;
    shbi.lParam     = (LPARAM)szPath;
    shbi.lpfn       = BrowseCallbackProc;

    OleInitialize(0);

    // show the browse-dialog!
    if((pidl = SHBrowseForFolder(&&shbi)) != 0)
    {
        // convert the selected ITEMIDLIST back to a path
        if(SHGetPathFromIDList(pidl, szPath))
            success = TRUE;

        // free the ITEMIDLIST
        SHGetMalloc(&pMalloc);
        pMalloc->Free(pidl);
        pMalloc->Release();
    }
    OleUninitialize();
    return success;
}

The callback function looks like this.

?
int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
    if(uMsg == BFFM_INITIALIZED)
        SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
    return 0;
}

Note that the lpData value is passed straight to SendMessage. We know this is safe to do because we set the corresponding lParam field in the BROWSEINFO structure, which points to the path we want.

Explorer Context Menu

Many applications like to add entries to the Shell context-menu in Explorer. The common way to do this is by writing a Shell Extension DLL in ATL/COM, but this is alot of work when only a simple text-entry with no bitmaps/icons is required.

The code below shows how to add a "Open with MyProgram" entry to all file-types in the system, so that when a file is right-clicked and the "Open with..." option selected, the application will be spawned with the selected filename passed as the first commandline argument:

?
#define CONTEXT_APP_LOC _T("Software\\Classes\\*\\shell\\Open With MyApp")
#define CONTEXT_CMD_LOC _T("Software\\Classes\\*\\shell\\Open With MyApp\\command")
?
void AddToExplorerContext()
{
    TCHAR szAppPath[MAX_PATH];
    TCHAR szDefaultStr[MAX_PATH];
    GetModuleFileName(0, szAppPath, MAX_PATH);
    wsprintf(szDefaultStr, _T("\"%s\" \"%%1\""), szAppPath);
    RegSetValue( HKEY_CURRENT_USER,
                 CONTEXT_CMD_LOC,
                 REG_SZ,
                 szDefaultStr,
                 lstrlen(szDefaultStr) * sizeof(TCHAR)
           );
}

The code above sets the "command" key to the following value:

?
<application-path> "%1"

To remove the menu-item use the following function:

?
void RemoveFromExplorerContext()
{
    RegDeleteKey(HKEY_CURRENT_USER, CONTEXT_CMD_LOC);
    RegDeleteKey(HKEY_CURRENT_USER, CONTEXT_APP_LOC);
}

Force Window onto visible display

If you use dual (or even triple/quad) displays then you have undoubtably encountered the following situation: You change the physical order of your displays, or otherwise reconfigure the logical ordering using your display software. This sometimes has the side-effect of changing your desktop coordinates from zero-based to negative starting coordinates (i.e. the top-left coordinate of your desktop changes from 0,0 to -1024,-768).

This effects many Windows programs which restore their last on-screen position whenever they are started. Should the user reorder their display configuration this can sometimes result in a Windows program subsequently starting in an off-screen position (i.e. at a location that used to be visible) - and is now effectively invisible, preventing the user from closing it down or otherwise moving it back on-screen.

?
void ForceVisibleDisplay(HWND hwnd)
{
    RECT rect;
    GetWindowRect(hwnd, &rect);
    // check if the specified window-rectangle is visible on any display
    if(NULL == MonitorFromRect(&rect, MONITOR_DEFAULTTONULL))
    {
        HMONITOR hMonitor;
        MONITORINFO mi = { sizeof(mi) };
        // find the nearest display to the rectangle
        hMonitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST);
        GetMonitorInfo(hMonitor, &mi);
        // center window rectangle
        rect.left = mi.rcWork.left + ((mi.rcWork.right - mi.rcWork.left) - (rect.right-rect.left)) / 2;
        rect.top  = mi.rcWork.top  + ((mi.rcWork.bottom - mi.rcWork.top) - (rect.bottom-rect.top)) / 2;
        SetWindowPos(hwnd, 0, rect.left, rect.top, 0, 0, SWP_NOACTIVATE|SWP_NOZORDER|SWP_NOSIZE);
    }
}

The ForceVisibleDisplay function can be called at program start-time right after the main window has been created and positioned 'on-screen'. Should the window be positioned in an off-screen position, it is forced back onto the nearest display to its last position. The user will be unaware this is happening and won't even realise to thank you for keeping their user-interface visible, even though they changed their display settings. Thanks to Norman Diamond for spottng the problems with the last version, this one will hopefully work a little better!

Change the System Highlight colour

It is possible to change the "system highlight" colour on a per-application basis. For example, suppose you wanted to change the Edit control's selection colour just for your application, so that it matched the rest of your custom controls. Using this technique it is possible to do this.

?
#include <windows.h>
#include <detours.h>
DETOUR_TRAMPOLINE(
    COLORREF WINAPI GetSysColorTrampoline(UINT),
    GetSysColor
);
COLORREF WINAPI GetSysColorDetour(UINT uIndex)
{
    if(uIndex == COLOR_HIGHLIGHT)
        return RGB(255, 0, 255);  // your_rgb_value_here
    else
        return GetSysColorTrampoline(uIndex);
}
void Init()
{
    DetourFunctionWithTrampoline(
        (PBYTE)GetSysColorTrampoline,
        (PBYTE)GetSysColorDetour);
}

The technique works by installing a Detour on the GetSysColor API (for the current application only). When the edit control draws it's selection colour, it calls this routine and the detour above intercepts it and returns a different value. You need the Detours library from microsoft for this to compile.

New XP-style ListViews

To enable the new XP visual style in ListViews (the translucent-blue selection rectangle) you need to do two things. Firstly, add an XP-Theme manifest to your executable to get the new common-controls 6.0 library. Then enable "double-buffering" mode for the ListView:

?
ListView_SetExtendedListViewStyle(hwndListView, LVS_EX_DOUBLEBUFFER);

ListView Groups

To use ListView groups you must first enable group-view mode:

?
ListView_EnableGroupView(hwndList, TRUE);

Adding a "group" requires the following code:

?
void AddListViewGroup(HWND hwndList, WCHAR *szText, int iGroupId)
{
    LVGROUP lvgrp = { sizeof(lvgrp) };
    lvgrp.mask      = LVGF_HEADER | LVGF_GROUPID | LVGF_ALIGN;
    lvgrp.pszHeader = szText;
    lvgrp.cchHeader = wcslen(lvgrp.pszHeader);
    lvgrp.iGroupId  = iGroupId;
    lvgrp.uAlign    = LVGA_HEADER_CENTER;
    ListView_InsertGroup(hwndList, 0, &lvgrp);
 }
?
AddListViewGroup(hwndList, L"Group Title", 12345);

New ListView items must be added into a specific group otherwise they don't appear.

?
void AddItemToListGroup(HWND hwndList, TCHAR *szItem, int iItemIdx, int iImageIdx, int iGroupId)
{
    LVITEM lvitem;
    lvitem.mask      = LVIF_IMAGE | LVIF_TEXT | LVIF_GROUPID;
    lvitem.iItem     = iItemIdx;
    lvitem.iSubItem  = 0;
    lvitem.pszText   = szItem;
    lvitem.iImage    = iImageIdx;
    lvitem.iGroupId  = iGroupId;    ListView_InsertItem(hwnd, &lvitem);
}
?
AddItemToListViewGroup(hwndList, L"Item 0", 0, 77, 12345);
AddItemToListViewGroup(hwndList, L"Item 1", 1, 88, 12345);

User Profile directory

Obtain the path of where the user-profiles are stored. i.e. "C:\Documents And Settings" or "C:\WINNT\Profiles".

?
//
// Find out where the user-profile directory is located
// i.e. C:\Documents And Settings
//      C:\WINNT\Profiles
//
BOOL GetProfilePath(TCHAR *szProfilePath, ULONG nLength)
{
    HKEY hKey;
    TCHAR szPath[MAX_PATH];
    ULONG nPathLen = sizeof(szPath);
    TCHAR *szProfileList = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList";
    if(ERROR_SUCCESS != RegOpenKeyEx(HKLM, szProfileList, 0, KEY_READ, &hKey))
        return FALSE;
    if(ERROR_SUCCESS == RegQueryValueEx(hKey, "ProfilesDirectory", 0, REG_SZ, szPath, &nPathLen))
    {
        ExpandEnvironmentStrings(szPath, szProfilePath, nLength);
        RegCloseKey(hKey);
        return TRUE;
    }
    else
    {
        RegCloseKey(hKey);
        return FALSE;
    }
}

Parent Process Id

Find out which process started you with this handy routine (Windows NT/2000/XP only).

?
ULONG GetParentPid(HANDLE hProcess)
{
    PROCESS_BASIC_INFORMATION pbi = { 0 };
    // Query the ProcessBasicInformation(0)
    ZwQueryProcessInformation(hProcess, 0, &pbi, sizeof(pbi), 0);
    return pbi.InheritedFromUniqueProcessId;
}

The code above makes use of an undocumented "Native API" called ZwQueryInformationProcess. This API is exported by NTDLL.DLL so you can easily locate it using GetModuleHandle / GetProcAddress. The function-pointer prototype is shown below:

?
ULONG (WINAPI * ZwQueryInformationProcess) (
                IN    HANDLE ProcessHandle,
                IN    ULONG ProcessInformationClass,
                OUT   PVOID ProcessInformation,
                IN    ULONG ProcessInformationLength,
                OUT   PULONG ReturnLength  OPTIONAL    );

The PROCESS_BASIC_INFORMATION structure is also not a standard PlatformSDK structure. It's definition is shown here:

?
typedef struct
{
     ULONG ExitStatus;
     PVOID PebBaseAddress;
     ULONG AffinityMask;
     ULONG BasePriority;
     ULONG_PTR UniqueProcessId;
     ULONG_PTR InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION;

Note that the function/structure definitions I have shown are slightly different to how they appear in other sources - I have changed all of the "NT" types to simple ULONG/PVOID types to make compilation easier.

?
// example:
DWORD dwParentId = GetParentPid(GetCurrentProcess());

Process Id from HANDLE

The exact same technique can be used to obtain the process ID from a process-handle, given a HANDLE to any process in the system. To do this, access the UniqueProcessId member inside the PROCESS_BASIC_INFORMATION structure, instead of the parent-id.

?
ULONG ProcessIdFromHandle(HANDLE hProcess)
{
    PROCESS_BASIC_INFORMATION pbi = { 0 };
    // Query the ProcessBasicInformation(0)
    ZwQueryProcessInformation(hProcess, 0, &pbi, sizeof(pbi), 0);
    return pbi.UniqueProcessId;
}
?
DWORD dwProcessId = ProcessIdFromHandle(hProcess);

Find where the user-kernel divide is located

Usually the system address-range starts at 0x80000000. However it is dangerous to assume this because of the ability to boot Windows with the /3Gb switch. The snippet below shows how to retrieve the "system range start" for the current system.

?
ULONG GetSystemRangeStart()
{
    ULONG nRangeStart = 0;
    // get the SystemRangeStartInformation (50) value
    ZwQuerySystemInformation(50, &nRangeStart, sizeof(ULONG));
    return nRangeStart;
}

argv[] and argc from a Windows program

Many people don't realise that it is possible to access the "traditional" argv[] commandline from a Windows GUI program. The Visual C compiler provides the program's commandline as global - in the __argc and __argv variables. These are the ANSI (char*) commandline variables. To obtain the Unicode (WCHAR*) commandline the GetCommandLineW API can be called.

Combined together we can write a routine which returns the commandline:

?
TCHAR **GetArgvCommandLine(int *argc)
{
#ifdef UNICODE
    return GetCommandLineW(argc);
#else
    *argc = __argc;
    return __argv;
#endif
}

The following program shows how to access argv and argc directly from WinMain:

?
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmdLine, int nCmdShow)
{
    TCHAR *argv;
    int argc;
    // obtain command-line arguments in argv[] style array
    argv = GetArgvCommandLine(&argc);
    // process the argv array
    for(int i = 0; i < argc; i++)
    {
        OutputDebugString(argv[i]);
    }    return 0;
}

Detect if an executable was started from a Short-Cut

This tip is copied from another article I wrote - Undocumented CreateProcess - and shows you is how any win32 application can detect if it was started from a shortcut (i.e. by double-clicking a shortcut link), or directly via Windows explorer or the Run dialog. This method also allows you to obtain the path of the shortcut link as well.

There is an undocumented STARTUPINFO flag which I have called STARTF_TITLESHORTCUT. This bit-flag has the value 0x800. Windows sets this flag when an executable has been started via a short-cut. So it is possible for any program to check if it was started this way by examining it's own STARTUPINFO structure to see if this flag was specified:

?
// Returns TRUE if started through a shortcut, FALSE if not. // Also returns the name of the shortcut file (if any)
BOOL GetShortcutName(TCHAR *szLinkName, UINT nLinkNameSize)
{
    STARTUPINFO si = { sizeof(si) };
    GetStartupInfo(&si);
    if(si.dwFlags & STARTF_TITLESHORTCUT)
    {
        lstrcpyn(szLinkName, si.lpTitle, nLinkNameSize);
        return TRUE;
    }
    return FALSE;
}

It is a simple matter to check for this undocumented flag, but another important piece of information should be explained. When the STARTF_TITLESHORTCUT flag is set, the lpTitle member of STARTUPINFO points to a string which contains the full path to the shortcut file used to start the current executable.

So imagine that there is a short-cut on your desktop which launches notepad.exe. When notepad.exe starts, it's STARTUPINFO::lpTitle contains the following text:

?
C:\Documents and Settings\James\Desktop\Notepad.lnk

The following snippet shows how to use GetShortcutName :

?
char linkName[MAX_PATH];
if(GetShortcutName(&linkName, MAX_PATH))
{
    printf("started from %s\n", linkName);
}

Resolve shortcut

Sometimes it is necessary to resolve a shell-shortcut link to the referenced file. The Win32 C-code below shows how.

?
//
//  Resolve a ShellLink (i.e. c:\path\shortcut.lnk) to a real path
//
BOOL ResolveShortcut(TCHAR *pszShortcut, TCHAR *pszFilePath, int nPathLen)
{
    IShellLink * psl;
    SHFILEINFO   info;
    IPersistFile *ppf;
    *pszFilePath = 0;
    // assume failure
    if((SHGetFileInfo(pszShortcut, 0, &info, sizeof(info), SHGFI_ATTRIBUTES) == 0))
        return FALSE;
    // not a shortcut?
    if(!(info.dwAttributes & SFGAO_LINK))
    {
        lstrcpyn(pszFilePath, pszShortcut, nPathLen);
        return TRUE;
    }
    // obtain the IShellLink interface
    if(FAILED(CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLink, (LPVOID*)&psl)))
        return FALSE;
    if (SUCCEEDED(psl->lpVtbl->QueryInterface(psl, &IID_IPersistFile, (LPVOID*)&ppf)))
    {
        if (SUCCEEDED(ppf->lpVtbl->Load(ppf, pszShortcut, STGM_READ)))
        {
            // Resolve the link, this may post UI to find the link
            if (SUCCEEDED(psl->lpVtbl->Resolve(psl, 0, SLR_NO_UI )))
            {
                psl->lpVtbl->GetPath(psl, pszFilePath, nPathLen, NULL, 0);
                ppf->lpVtbl->Release(ppf);
                psl->lpVtbl->Release(psl);
                return TRUE;
            }
        }
        ppf->lpVtbl->Release(ppf);
    }
    psl->lpVtbl->Release(psl);
    return FALSE;
}

Return values from a DialogProc

Returning a value from a dialog-procedure is not as simple as a normal window-procedure. A DialogProc only returns TRUE/FALSE to indicate to Windows whether or not the message was processed. To return a 'real' value from a DialogProc requires calling the SetWindowLongPtr API with the DWLP_MSGRESULT parameter.

A common example is processing the WM_CTLCOLOREDIT message inside a dialog-box:

?
case WM_CTLCOLOREDIT:
    // set foreground/background colours as normal
    SetTextColor(hdc, rgbForeground);
    SetBkColor(hdc, rgbBackground);
    // 'return' the brush from the dialog-procedure
    SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)hBrush);
    // tell Windows that we processed this dialog-message
    return TRUE;

The SetWindowLong API is used in this situation to return the HBRUSH back to Windows.

Small icons for titlebars

Setting the icon to appear in a window's titlebar is not as simple as calling LoadIcon, because even though your icon resource might contain a 16x16 image, LoadIcon will only load the 32x32 image size. The following snippets shows how to call RegisterClassEx using LoadImage to set both the small and large icons for a window's titlebar:

?
void InitWindow()
{
    WNDCLASSEX wcx;
    ....
    wcx.hIcon    = LoadImage(hInst, MAKEINTRESOURCE(IDI_APP), IMAGE_ICON, 32, 32, LR_CREATEDIBSECTION);
    wcx.hIconSm  = LoadImage(hInst, MAKEINTRESOURCE(IDI_APP), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
    RegisterClassEx(&wcx);
}

The alternative method is to use the SendMessage function after the window has been created, and use the WM_SETICON message, specifying the ICON_BIG and ICON_SMALL parameters where appropriate.

14 May 2006

Posted in Tutorials

win32
Powered by Drupal


C & C++ Tricks | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

C & C++ Tricks

This section contains code snippets which I have acquired over time. Most of the code samples are in the C programming language. Just a couple require C++. Wherever possible, I have accredited the code with the respective authors. However, there are quite a few samples of which I have lost the original author's details. In these cases I have left the snippets unaccredited. If you see a code snippet below, and you believe you know who originally published it, please drop me a line and I'll update this page immediately.

Please send any comments or suggestions to:

Bit Twiddling

Is an integer number a power of 2?

#define ISPOW2(x)  (x && !(x & (x-1)))

#define ISPOW2(n)  (!( n & (n-1))

#define ISPOW2(x)  ((~(~0U >> 1) | x) & x - 1)

Swap two integer numbers

int a, b;
...
a ^= b;
b ^= a;
a ^= b;

...
/* note that this is not portable */
a ^= b ^= a ^= b;

Detect if there is a zero byte in a 32bit word:

#define has_null_byte(x) (((x) - 0x01010101) & ~(x) & 0x80808080)

Divide an integer number by 15

unsigned div15(unsigned x)
{
    unsigned s,t;
    s = x >> 4;
    t = (x & 0xf) + s + 1;
    t += t >> 4;
    t = t + (t >> 8) >> 4) + s;
}

unsigned short div15(unsigned short x)
{
    unsigned t;
    t = x + 1;
    t += t << 4;
    t += t >> 8;
    t += t >> 16;
    return (unsigned short)(t >> 8);
}

long div15(long x)
{

    long s = (x >> 4) + (x >> 8);
    s += s >> 8;
    s += s >> 8;
    return s;
}

Bit Counting

Count the number of bits in an integer

int countbits(unsigned long num)
{
    int count;

    for(count = 0; num; num &= num - 1) count++;

    return count;
}

Using a lookup table:

int lookup[256];

void buildlookup(void)
{

    int i;
    for(i = 0; i < 256; i++)
    {
        unsigned char t = i;
        lookup[i] = 0;
        for(int j = 0; j < 8; j++)
        {
            if(t & 1) lookup[i]++;
            t >>= 1;
        }
    }
}

int countbits(int x)
{

    int count = lookup[x & 0xf] +
            lookup[(x >> 8) & 0xf] +
            lookup[(x >> 16) & 0xf] +
            lookup[(x >> 24) & 0xf];

    return count;
}

Using some ingenious bit-twiddling

/*  Bit counter by Ratko Tomic */
int countbits(long i)

{
      i = ((i & 0xAAAAAAAAL) >>  1) + (i & 0x55555555L);
      i = ((i & 0xCCCCCCCCL) >>  2) + (i & 0x33333333L);
      i = ((i & 0xF0F0F0F0L) >>  4) + (i & 0x0F0F0F0FL);
      i = ((i & 0xFF00FF00L) >>  8) + (i & 0x00FF00FFL);
      i = ((i & 0xFFFF0000L) >> 16) + (i & 0x0000FFFFL);
      return (int)i;
}

Number Rounding

Round an integer up to the nearsest alignment boundary.

n = (n + ALIGN - 1) - (n + ALIGN - 1) % ALIGN;

num = mod * ((num + mod - 1) / mod);

Assuming the mod is a power of 2, the rounding can take place using bitwise operators .

num = (num + mod - 1) & ~(mod - 1);

Base conversion

A FAST cast from float to long

inline long float2long(float d)
{
    #define MAGIC (((65536.0 * 65536.0 * 16.0) + 32768.0) * 65536.0)
    double dtemp = d + MAGIC;
    return (*(long *) &dtemp) - 0x80000000;
}

String & Memory routines

int memcmp2(void *mem1, void *mem2, size_t length)
{
    unsigned char *mptr1 = (unsigned char *)mem1;
    unsigned char *mptr2 = (unsigned char *)mem2;

    if(*mptr1++ != *mptr2++) return !0;

    unsigned char tempch = mem1[length-1];

    mem1[length-1] = ~mem2[length-1];

    /* do the comparison */
    while(*mptr1++ != *mptr2++);

    mem1[length-1] = tempch;

    if(mptr1 == (mem1 + length - 1))
        return 0;
    else
        return 1;
}

A weird memory copy, using Duff's Device to check the loop termination 1 in every 8 iterations

switch(length & 0x7)
{
    do
    {
    case 7:  *dest++ = *src++;
    case 6:  *dest++ = *src++;
    case 5:  *dest++ = *src++;
    case 4:  *dest++ = *src++;
    case 3:  *dest++ = *src++;
    case 2:  *dest++ = *src++;
    case 1:  *dest++ = *src++;
    case 0:
    } while(0 <= length -= 8);
}

Miscallaneous

The weirdest C program? Requires 8086 family of processors.

int main[] = { 0xc3 };

Compiles as you type! Requies a unix tty terminal

#include "/dev/tty0"

Halt with a compile error if the target platform's word size is not 32bits

int error_not_32bits[sizeof(int) == 4];

Calculate if a year is a leap year or not

/* 'The C Programming Language' by Kernigan & Richie */
int isleapyear(int year)
{
    return (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);
}

Work out what the day of the week is for a given date

/* Tomohiko Sakamoto */
int dayofweek(int d, int m, int y)
{
    static int t[] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 };

    y -= m < 3;
    return ( y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
}

A C program which source is the same as its output (format as one line)

char *s = "char *s = %c%s%c;
main(){printf(s,34,s,34);}";
main(){printf(s,34,s,34);}

Calculate the value of PI

double PI = 4.0 * atan(1.0);

Calculate a random number in the range 0..N

bad:  int r = rand() % N;                  /* not very random */
better: int r = rand() / (RAND_MAX / N + 1);

An even better method (i.e. more random) was suggested by Jerry Coffin - thanks!

int rand_lim(int limit)
{
    /* return a random number between 0 and limit inclusive. */

    int divisor = RAND_MAX / (limit+1);
    int retval;

    do
    {
        retval = rand() / divisor;
    } while (retval > limit);

    return retval;
}

22 Jan 2012

Posted in Tutorials

Powered by Drupal


Insert buttons into an edit control | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
editbut.zip

Published on 22 Jan 2012

7.54 Kb

Download

Insert buttons into an edit control

How to insert a button into an edit control

This tutorial will show you how to insert a button into the non-client area of an edit control. The effect of this is very similar to the drop-down button in a combo box. However, there is the potential to do much more than simply display a drop-down arrow. The technique of inserting a button can actually be applied to any type of window. For this example we will use an edit control, but any other window could be used.

The picture below will give you an idea of what we are trying to achieve.

Edit button screen-shot

Subclassing the edit control

The key to this technique is subclassing. We will replace the window procedure of our edit control with a new procedure. This procedure will perform some additional tasks to implement the inserted button, and then use the default window procedure to provide the original functionality of the edit control.

Our inserted button will need to have some kind of state. We need to know where the button will live, and also if it is pressed in or out. We might also want to store a command identifier for the button, so that we can generate WM_COMMAND messages when the button is clicked. It will therefore be necessary to associate this state information with what ever window we decide to subclass. The following structure will hold our state:

?
typedef struct
{
    UINT uCmdId;       // sent in a WM_COMMAND message
    UINT fButtonDown;  // is the button up/down?
    BOOL fMouseDown;   // is the mouse activating the button?
    WNDPROC oldproc;   // need to remember the old window procedure
    // size of the current window borders.
    // given these, we know where to insert our button
    int cxLeftEdge, cxRightEdge;
    int cyTopEdge,  cyBottomEdge;
} InsBut;

Now that we know how our button will be represented, we can write a function to subclass the edit control.

?
BOOL InsertButton(HWND hwnd, UINT uCmdId)
{
    InsBut *pbut;
    pbut = HeapAlloc(GetProcessHeap(), 0, sizeof(InsBut));
    if(!pbut) return FALSE;
    pbut->uCmdId = uCmdId;
    pbut->fButtonDown = FALSE;
    // replace the old window procedure with our new one
    pbut->oldproc = (WNDPROC)SetWindowLong(hwnd, GWL_WNDPROC, (LONG)InsButProc);
    // associate our button state structure with the window
    SetWindowLong(hwnd, GWL_USERDATA, (LONG)pbut);
    // force the edit control to update its non-client area
    SetWindowPos(hwnd, 0, 0, 0, 0, 0, SWP_FRAMECHANGED |
        SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE|SWP_NOZORDER);
    return TRUE;
}

Inserting the button

Now that our new window procedure is in place we can get to work. The first step is to allocate some space in the non-client area of the edit control. This is performed by handling the WM_NCCALCSIZE message in the subclass procedure.

?
RECT *prect;
RECT oldrect;
// get the button state structure
InsBut *pbut = (InsBut *)GetWindowLong(hwnd, GWL_USERDATA);
...
case WM_NCCALCSIZE:
    prect = (RECT *)lParam;
    oldrect = *prect;
    // let the old wndproc allocate space for the borders,
    // or any other non-client space.
    CallWindowProc(pbut->oldproc, hwnd, msg, wParam, lParam);
    // calculate what the size of each window border is,
    // we need to know where the button is going to live.
    pbut->cxLeftEdge   = prect->left     - oldrect.left;
    pbut->cxRightEdge  = oldrect.right   - prect->right;
    pbut->cyTopEdge    = prect->top      - oldrect.top;
    pbut->cyBottomEdge = oldrect.bottom  - prect->bottom;
    // now we can allocate additional space by deflating the
    // rectangle even further. Our button will go on the right-hand side,
    // and will be the same width as a scrollbar button
    prect->right -= GetSystemMetrics(SM_CXVSCROLL);
    // that's it! Easy or what!
    return 0;

The source above firstly calls the old window procedure of the edit control, to let the default action of border allocation take place. After this has been done, we can then reserve space for our new button.

Drawing the button

Drawing the button is easy now that we have the space allocated to draw it in. Because we are drawing the button in the non-client area of a window, we need to handle the WM_NCPAINT message.

The first thing to do is obtain a handle to a device context. We do this with GetWindowDC. Because this is a window-DC (rather than a client-area DC), all drawing takes place from the upper-left corner of the window (NOT the client area of the window). All coordinates start from 0,0.

?
// retrieve the coordinates of an inserted button, given the
// specified window rectangle.
void GetButtonRect(InsBut *pbut, RECT *rect)
{
    rect->right  -= pbut->cxRightEdge;
    rect->top    += pbut->cyTopEdge;
    rect->bottom -= pbut->cyBottomEdge;
    rect->left    = rect->right - GetSystemMetrics(SM_CXVSCROLL);
    // take into account any scrollbars in the edit control
    if(rect->cxRightEdge > rect->cxLeftEdge)
        OffsetRect(rect, pbut->cxRightEdge - pbut->cxLeftEdge, 0);
}
?
HDC hdc;
RECT rect;
// get the button state structure
InsBut *pbut = (InsBut *)GetWindowLong(hwnd, GWL_USERDATA);
...
case WM_NCPAINT:
    // let the old window procedure draw the borders / other non-client
    // bits-and-pieces for us.
    CallWindowProc(pbut->oldproc, hwnd, msg, wParam, lParam);
    // get the screen coordinates of the window.
    // adjust the coordinates so they start from 0,0
    GetWindowRect(hwnd, &rect);
    OffsetRect(&rect, -rect.left, -rect.top);
    // work out where to draw the button
    GetButtonRect(pbut, &rect);
    hdc = GetWindowDC(hwnd);
    // now draw our inserted button:
    // draw a 3d-edge around the button.
    if(pbut->fButtonDown)
        DrawEdge(hdc, &rect, EDGE_RAISED, BF_RECT | BF_FLAT | BF_ADJUST);
    else
        DrawEdge(hdc, &rect, EDGE_RAISED, BF_RECT | BF_ADJUST);
    // fill the inside of the button
    FillRect(hdc, &rect, GetSysColorBrush(COLOR_BTNFACE));
    ReleaseDC(hwnd, hdc); // that's it! This is too easy!
    return 0;

Bringing the button to life

A static button in an edit control isn't much use to us. The last step is therefore to handle the appropriate mouse messages to make the button "clickable". We need to handle three mouse messages:

Mouse button down (WM_NCLBUTTONDOWN)

When the left mouse button is clicked, and the mouse cursor is within the area occupied by our button, then we need to react to this event and give the button a depressed look. We will also set the mouse capture so we can get all further mouse messages whilst the mouse button is held down. This means that we can move the mouse outside the window and can still retrieve mouse messages and react to them accordingly.

?
RECT rect;
POINT pt;
...
case WM_NCLBUTTONDOWN:
    // get the screen coordinates of the mouse
    pt.x = GET_X_LPARAM(lParam);
    pt.y = GET_Y_LPARAM(lParam);
    // get the position of the inserted button
    GetWindowRect(hwnd, &rect);
    GetButtonRect(pbut, &rect);
    // check that the mouse is within the inserted button
    if(PtInRect(&rect, pt))
    {
        SetCapture(hwnd);
        pbut->uState = 1;
        pbut->fMouseActive = TRUE;
        //redraw the non-client area to reflect the change
        RedrawNC(hwnd);
    }
    break;

Mouse Movement (WM_MOUSEMOVE)

When a window has captured the mouse, it will not receive any more non-client mouse messages. So, if we want to react to the mouse moving (with the capture set), we must handle WM_MOUSEMOVE, and NOT the WM_NCMOUSEMOVE message.

When the mouse moves, we need to check the position of the cursor again. If the cursor is inside the button, then we will make it look "depressed". If the mouse moves outside the button, then the button must automatically pop back up again. This emulates the way a normal button in Windows works.

?
RECT rect;
POINT pt;
UINT oldstate;
...
case WM_MOUSEMOVE:
    if(pbut->fMouseActive == FALSE)
        break;
    // get the CLIENT coordinates of the mouse
    pt.x = GET_X_LPARAM(lParam);
    pt.y = GET_Y_LPARAM(lParam);

    ClientToScreen(hwnd, &pt);
    // get the position of the inserted button
    GetWindowRect(hwnd, &rect);
    GetButtonRect(pbut, &rect);
    oldstate = pbut->uState;
    // check that the mouse is within the inserted button
    if(PtInRect(&rect, pt))
        pbut->uState = 1;
    else
        pbut->uState = 0;
    // redraw the non-client area to reflect the change.
    // to prevent flicker, we only redraw the button if its state
    // has changed
    if(oldstate != pbut->uState)
        RedrawNC(hwnd);
    break;

Mouse button up (WM_LBUTTONUP)

We're almost there. All there is to do is detect when the mouse button is released. We do one of two things when this happens. If the mouse is released when it is over the button, then the button has been clicked. We can therefore post a WM_COMMAND message to the parent window. If the mouse is outside the button when it is released, then we do nothing. In both cases, we redraw the button in its normal state, and release the mouse capture.

?
RECT rect;
POINT pt;
UINT oldstate;
...
case WM_LBUTTONUP:
    if(pbut->fMouseActive == FALSE)
        break;
    // get the CLIENT coordinates of the mouse
    pt.x = GET_X_LPARAM(lParam);
    pt.y = GET_Y_LPARAM(lParam);
    ClientToScreen(hwnd, &pt);
    // get the position of the inserted button
    GetWindowRect(hwnd, &rect);
    GetButtonRect(pbut, &rect);
    // check that the mouse is within the inserted button
    if(PtInRect(&rect, pt))
    {
        PostMessage(GetParent(hwnd), WM_COMMAND,
                    MAKEWPARAM(pbut->uCmdId, BN_CLICKED), 0);
    }
    ReleaseCapture();
    pbut->uState     = 0;
    pbut->fMouseDown = FALSE;
    // redraw the non-client area to reflect the change.
    RedrawNC(hwnd);
    break;

There is one last mouse message to process. We need to handle the WM_NCHITTEST message, which we receive when the mouse moves over the window. We have to return a code indicating that our inserted button region is a valid window portion. If we don't do this, then we won't get any mouse messages when the mouse moves over the button area.

?
case WM_NCHITTEST:
    // get the screen coordinates of the mouse
    pt.x = GET_X_LPARAM(lParam);
    pt.y = GET_Y_LPARAM(lParam);
    // get the position of the inserted button
    GetWindowRect(hwnd, &rect);
    GetButtonRect(pbut, &rect);
    // check that the mouse is within the inserted button
    if(PtInRect(&rect, pt))
        return HTBORDER;
    else
        break;

Wrapping Up

The last step to perform is to clean up after ourselves when the edit control gets destroyed. Because we allocated a structure using HeapAlloc, we must free it using HeapFree. The best place to do this is during the processing of the WM_NCDESTROY message. This is the very last message that a window receives before it is destroyed.

?
WNDPROC oldproc;
...
case WM_NCDESTROY:
    oldproc = pbut->oldproc;
    HeapFree(GetProcessHeap(), 0, pbut);
    return CallWindowProc(oldproc, hwnd, msg, wParam, lParam);

You will notice that the old procedure handle is copied into a temporary variable. We do this because the oldproc member in the button structure becomes invalid as soon as the memory to it is released. This is the only time that the old window procedure isn't called through the pbut->oldproc member.

Conclusion

I hope you have found this tutorial useful. There are still a few things that you can do to improve the inserted button. For a start, it doesn't have any type of image associated with it. Your next step would be to modify the drawing code to draw whatever effect you want. One possibility would be to draw a set of ellipses (...) on the button. This would then turn the inserted button into a very effective "Browse for file" button. Alternatively, your inserted button could toggle between several different states. For example, the button could control a number displayed in an edit box, and toggle in between decimal and hexadecimal number systems.

The technique presented here can be applied to any sort of window, and can be used to perform many different types of effects. This very technique is the one used by the Cool Scrollbar library, which you will also find in the tutorials section.

20 May 2001

Posted in Tutorials

win32
Powered by Drupal


Masked Edit Input | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
MaskedEdit.c

Published on 22 Jan 2012

2.05 Kb

Download

Masked Edit Input

How to filter specific keys-strokes from a standard edit control

This article will describe one method of implementing masked edit controls. There are loads of different ways to perform the masking operation, depending on how you want to restrict the user's input, but they all revolve around the same basic principles. The method presented here will allow an edit control to mask any ANSI character, thereby restricting the input to a subset of the ANSI character set.

Masking the input to an edit control requires that we somehow intercept keystroke messages that are sent to the control whenever a key is pressed. The best way to do this is to subclass the edit control. We can let all messages go un-modified to the original window procedure, but we can handle the WM_CHAR message to suit our needs.

The Masking Function

Firstly, we will define a function which will apply a mask to an edit control.

?
BOOL MaskEditControl(HWND hwndEdit, char szMask[], DWORD nOptions)

This function takes three parameters.

hwndEdit is the handle to an edit control created by the current process.szMask is a text string containing the characters to mask.nOptions will specify additional masking options for the edit control. A value of TRUE means that the specified text string contains valid characters, which the edit control will accept when typed in. A value of FALSE means that the specified text string contains characters which will be rejected when input to the control.

This provides a fairly flexible way to define a mask for an edit control. For example, the following snippet will mask the specified edit control, which will subsequently only accept numeric characters, the space character and back-slashes. Including the back-slash will enable the user to delete characters from the control.

?
MaskEditControl(hwndEdit, "0123456789 \b", TRUE);

Masking Function Details

The function itself is pretty straight-forward. The basic steps that we will take are listed here:

  • Retrieve the pointer to current window procedure using GetWindowLong(hwnd, GWL_WNDPROC)
  • Allocate a block of memory big enough to store this function pointer, and a 256 element BOOL array.
  • Build a lookup table in this array, with each array element corresponding to one ANSI character.
  • Replace the edit control's window procedure with our custom masking one.
  • Associate our block of memory with the edit control using SetWindowLong(hwnd, GWL_USERDATA, (LONG)data).

This is basically what the function looks like:

?
BOOL MaskEditControl(HWND hwndEdit, char szMask[], DWORD nOptions)
{
    BOOL *mask;
    WNDPROC oldproc;
    int i;
    // don't make a new mask if there is already one available
    oldproc = (WNDPROC)GetWindowLong(hwndEdit, GWL_WNDPROC);
    if(oldproc != MaskedEditProc)
        mask = (BOOL *)HeapAlloc(GetProcessHeap(), 0, sizeof(WNDPROC) + 256 * sizeof(BOOL));
    else
        mask = (BOOL *)GetWindowLong(hwndEdit, GWL_USERDATA);
    // build the mask lookup table. The method varies depending
    // on whether we want to allow or disallow the specified szMask characters
    if(nOptions == TRUE)
    {
        for(i = 0; i < 256; i++)
            mask[i] = FALSE;
        for(i = 0; szMask[i] != '\0'; i++)
            mask[szMask[i]] = TRUE;
    }
    else
    {
        for(i = 0; i < 256; i++)
            mask[i] = TRUE;
        for(i = 0; szMask[i] != '\0'; i++)
            mask[szMask[i]] = FALSE;
    }
    //don't update the user data if it is already in place
    if(oldproc != MaskedEditProc)
    {
        mask[256] = (BOOL)GetWindowLong(hwndEdit, GWL_WNDPROC);
        SetWindowLong(hwndEdit, GWL_WNDPROC, (LONG)MaskedEditProc);
        SetWindowLong(hwndEdit, GWL_USERDATA, (LONG)mask);
    }
    return TRUE;
}

Now that we have defined a suitable interface to the masked edit control, we can go on to write a replacement window procedure.

The Masked Edit Window Procedure

?
LRESULT CALLBACK MaskedEditProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    WNDPROC oldwndproc;
    BOOL *mask = (BOOL * )GetWindowLong(hwnd, GWL_USERDATA);
    oldwndproc = (WNDPROC)mask[256];
    switch(uMsg)
    {
    // WM_NCDESTROY is the LAST message that a window will receive -
    // therefore we must finally remove the old wndproc here
    case WM_NCDESTROY:
        HeapFree(GetProcessHeap(), 0, mask);
        break;
    case WM_CHAR:
        if(mask[wParam & 0xff] == TRUE)
            break;
        else
            return 0;
    }
    return CallWindowProc(oldwndproc, hwnd, uMsg, wParam, lParam);
}

That's it in a nutshell. There are lots of other ways of performing the masking operation itself. In this case I use a simple boolean lookup table, with one element corresponding to one character. The idea is the same no matter what method you choose to do the mask - subclass the edit control and associate some kind of mask with it.

21 May 2001

Posted in Tutorials

win32
Powered by Drupal


Introduction to Printing | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Introduction to Printing

How to print from a Windows application.

This tutorial will show you how to print a correctly, including how to calculate the page margins. We are going to let the standard print and page setup dialogs do most of the hard work for us, but there is still a little work to do. In the example I will present won't do anything fancy - just work out the page margins, calculate how many lines per page and how many pages to print, and lastly draw a few lines of text.

The Printer Device Context

Printing is performed by drawing to a device context, in exactly the same way graphics are drawn to a window's device context. The only difference is that the printer's device context will be a different aspect ratio to the screen, and will be much higher resolution as well.

The absolute best way to get a device context is to use the PrintDlg function, which is found in the common controls library. This function displays the standard Print dialog box which allows the user to select which printer to print to, how many pages to print and so on. PrintDlg requires a single argument - a pointer to a PRINTDLG structure. This structure is contains quite a few members, but we only need to use a few of them in this example. Here's how to set up the PRINTDLG structure and call PrintDlg itself:

?
PRINTDLG pd;
...
ZeroMemory(&pd, sizeof(pd));
pd.lStructSize = sizeof(pd);    // always remember to set this!
pd.hInstance = hInst;           // application process handle
pd.hwndOwner = hwnd;
pd.hDevMode  = hDevMode;
pd.hDevNames = hDevNames;
pd.Flags     = PD_ALLPAGES|PD_HIDEPRINTTOFILE|PD_NOPAGENUMS|PD_RETURNDC;
pd.nCopies   = 1;
...
// now we can show the print dialog
// if the user chose cancel, then quit out.
if(!PrintDlg(&pd))
    return FALSE;

Notice that PD_RETURNDC is set in the Flags member. This is how we get a device context to print into. When PrintDlg returns, the hDC member will contain a handle to a valid device context to print into.

You will also notice the two variables highlighted in the code. PrintDlg and PageSetupDlg both require these two handles each time they are called. If these members are set to NULL in the PRINTDLG structure, then PrintDlg will automatically allocate space for them, and store the handles in the hDevMode and hDevNames members when it returns. I recommend allocating these memory objects once at the start of your program, and storing the returned handles in two global variables. Then each time you need to call PrintDlg or PageSetupDlg for real, you can use the two saved handles. You can use the function below to allocate these two memory blocks. Note that PSD_RETURNDC is not set in the Flags member.

?
static HANDLE hDevMode, hDevNames;
...
HANDLE GetDevMode()
{
    PAGESETUPDLG ps;
    ZeroMemory(&ps, sizeof ps);
    ps.lStructSize = sizeof ps;
    ps.Flags       = PSD_RETURNDEFAULT;
    PageSetupDlg(&ps);
    CopyRect(&rcMargin, &ps.rtMargin);
    hDevMode = ps.hDevMode;
    hDevNames = ps.hDevNames;
    return ps.hDevMode;
}

Calculating the margins

It is more than likely that you will want to set up the margins on the printed page to correspond with the user's margin settings.

The first thing to realise is that most (if not all) printers cannot print to the edge of a piece of paper. There will always be a small border around the outside of this printable area which cannot be printed to. The printable area of a printing device is represented by four numbers, which can be obtained using the GetDeviceCaps API call.

PHYSICALWIDTH | The width of the physical page, in device units.
PHYSICALHEIGHT | The height of the physical page, in device units.
PHYSICALOFFSETX | The distance from the left edge of the physical page to the left edge of the printable area, in device units.
PHYSICALOFFSETY | The distance from the top edge of the physical page to the top edge of the printable area, in device units.

Assuming that the user selects a 1 inch margin around the page, we must calculate the position of the page area. The real margin will be the user's margin minus the size of the unprintable area. The picture below shows the relationship between the page area, the unprintable area and the margin size.

Page-margins

Right, on with the coding. The first step is to convert the current user-selected page margins into device units. We do this because currently the margins are specified using either inches or millimetres. So, we need to convert these units into something that is meaningful to the printer device.

?
margins.left   = MulDiv(rcMargin.left,   GetDeviceCaps(pd.hDC, LOGPIXELSX), 1000);
margins.top    = MulDiv(rcMargin.top,    GetDeviceCaps(pd.hDC, LOGPIXELSY), 1000);
margins.right  = MulDiv(rcMargin.right,  GetDeviceCaps(pd.hDC, LOGPIXELSX), 1000);
margins.bottom = MulDiv(rcMargin.bottom, GetDeviceCaps(pd.hDC, LOGPIXELSY), 1000);

Once we know physically how big the margins should look like, we need to adjust these margins to take into account the border around the outside of the printable area. We do this by subtracting the physical borders from the user-defined borders. This leaves us with a set of offsets which can be added onto each edge of the printable area to finally yield the correct borders.

?
int iLeftAdjust   = margins.left  - iPhysOffsetX;
int iTopAdjust    = margins.top   - iPhysOffsetY;
int iRightAdjust  = margins.right - (iPhysWidth  - iPhysOffsetX - GetDeviceCaps(pd.hDC, HORZRES));
int iBottomAdjust = margins.right - (iPhysHeight - iPhysOffsetY - GetDeviceCaps(pd.hDC, VERTRES));

The final thing to do is to work out how big an area we have to print to, taking into account the borders we have just worked out. The code below tells us how big each page is. Remember that we can still print outside of this area - these are self-imposed printing limits rather than physical limits.

?
iWidth  = GetDeviceCaps(pd.hDC, HORZRES) - (iLeftAdjust + iRightAdjust);
iHeight = GetDeviceCaps(pd.hDC, VERTRES) - (iTopAdjust + iBottomAdjust);

How many lines on a page?

Before we can print anything, we need to know how many lines of text we can fit on each page before we start the next one. To do this, we need to know how many lines will be printed in total. Only you know the answer to this one. The other piece of information we need is the height of a line of text on the printed page. We can get this by using the GetTextMetrics call.

?
TEXTMETRIC tm;
HFONT hfont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
int yChar;
// Setup the current device context
SetMapMode(pd.hDC, MM_TEXT);
SelectObject(pd.hDC, hfont);
// work out the character dimensions for the current font
GetTextMetrics(pd.hDC, &tm);
yChar = tm.tmHeight;

Now that we know the height of a single line of text (the yChar variable), we can calculate how many lines fit onto a single page.

?
//work out how much data can be squeezed onto each page
int iHeaderHeight   = 0;
int iTotalLines     = ???;
int iLinesPerPage   = (iHeight - iHeaderHeight) / yChar;
int iTotalPages     = (iTotalLines + iLinesPerPage - 1) / iLinesPerPage;

The iHeaderHeight variable could be used if you display some form of header at the top of each page (e.g. page numbering or a title). If you don't display anything here, then iHeaderHeight can be set to zero, or omitted completely.

Printing (at last!)

That all probably seems like alot of effort for nothing. However, we are now ready to print our page out. The method we use here is baed around the one Charles Petzold uses in his "Programming Windows" book. For more details I would advise you to refer to this.

?
DOCINFO di;
BOOL bSuccess = TRUE;
BOOL bUserAbort = FALSE;
...
ZeroMemory(&di, sizeof(di));
di.cbSize = sizeof(di);
di.lpszDocName = szFileName;    // name of the file you are printing.
                                // this will appear in the print manager
?
if(StartDoc(pd.hDC, &di) > 0)
{
    for(iColCopy = 0; iColCopy < ((pd.Flags & PD_COLLATE) ? pd.nCopies : 1); iColCopy++)
    {
        for(iPage = 0; iPage < iTotalPages; iPage++)
        {
            for(iNonColCopy = 0; iNonColCopy < ((pd.Flags & PD_COLLATE) ? 1 : pd.nCopies); iNonColCopy++)
            {
                HANDLE hold;
                if(StartPage(pd.hDC) < 0)
                {
                    bSuccess = FALSE;
                    break;
                }
                // Make all printing be offset by the amount specified for the margins
                SetViewportOrgEx(pd.hDC, iLeftAdjust, iTopAdjust, NULL);
                // select the fixed-width font into the printer DC
                hold = SelectObject(pd.hDC, hfont);
                //print the current file line by line
                for(iLine = 0; iLine < iLinesPerPage; iLine++)
                {
                    char szBuffer[200];
                    iLineNum = iLinesPerPage * iPage + iLine;
                    if(iLineNum > iTotalLines) break;
                    //get line (iLine) from the application, and store it
                    //into szBuffer
                    //output a line of text to the printer
                    TextOut(pd.hDC, 0, yChar * iLine + iHeaderHeight, szBuffer,

                    lstrlen(szBuffer) - 2);
                    bUserAbort = !QueryAbort();
                }
                SelectObject(pd.hDC, hold);
                if(EndPage(pd.hDC) < 0)
                {
                    bSuccess = FALSE;
                    break;
                }
                bUserAbort = !QueryAbort();
                if(bUserAbort) break;
            }
            if(!bSuccess || bUserAbort) break;
        }
        if(!bSuccess || bUserAbort) break;
    }
}
else
{
    bSuccess = FALSE;
}

The code above looks pretty complicated because of all the nested for loops. The printing is structured like this to take into account two important factors. The user may have selected to print out multiple copies of the same document, and might have also selected to collate (collect) pages together as well. The for-loops take care of these situations.

Cancelling Printing

What happens if the user want to cancel printing half-way through? This is what the QueryAbort function does. It runs its own message loop and detects if the user presses the Escape key.

?
UINT CALLBACK ab(size_w pos, size_w len)
{
    MSG msg;
    BOOL bAbort = FALSE;
    while(!bAbort && PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&msg);
        switch(msg.message)
        {
        case WM_QUIT:
            PostQuitMessage(0);
            bAbort = TRUE;
            break;
        case WM_KEYDOWN:
            if(msg.wParam == VK_ESCAPE)
            {
                bAbort = QueryAbortDlg();
                continue;
            }
            break;
        case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN:
            bAbort = QueryAbortDlg();
            continue;
        }
        DispatchMessage(&msg);
    }
    return !bAbort;
}

Cleaning Up

After the document has been printed (or printing cancelled), then we need to release the printer device context. We also need to commit the document to the printer spooler, or cancel the document if the user aborted for some reason.

?
if(bSuccess && !bUserAbort)
    EndDoc(pd.hDC);
else
    AbortDoc(pd.hDC);
DeleteDC(pd.hDC);

Conclusion

Well, I never said it was going to be easy. I ran out of steam at the end so I havn't described the actual printing process very well. For now I'd refer to Charles Petzold's book "Programming Windows 5th Ed". Maybe someday I'll update this tutorial, but for now this is how it stands.

22 May 2001

Posted in Tutorials

win32
Powered by Drupal


Fixed-width Font Enumeration | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
enumfixedfonts.c

Published on 22 Jan 2012

2.52 Kb

Download

Fixed-width Font Enumeration

How to enumerate all fixed-width fonts in a system along with their sizes

Enumerating fonts can be a little confusing, and unless you want to enumerate all fonts on your system, can be a little more difficult than MSDN suggests. This article will explain exactly the steps you need to use to find every fixed-width font on your system, and also enumerate every possible size for each individual font.

Enumerate fonts

The best function to use for font enumeration is EnumFontFamiliesEx. You must set up a LOGFONT structure which tells EnumFontFamilesEx what fonts you wish to enumerate. You only need to specify three members of this structure.

The lfCharSet member indicates which character set you want to select. The DEFAULT_CHARSET value indicates that all character sets (i.e. OEM, ANSI, Chinese etc) will be enumerated. You could always specify ANSI_CHARSET, but in this case we want OEM character sets as well.

The lfPitchAndFamily member describes the look of a font in a general way. In this case, we don't mind what the font will look like (FF_DONTCARE), because we want to enumerate all fonts anyway, right? The FIXED_PITCH value just tells Windows that we are only interested in fixed-width fonts.

Finally, the lfFaceName member is used to tell windows what font we want to enumerate. If you specify the name of a string here, then Windows will enumerate the available font sizes for that font. First of all though, we want to enumerate the font names themselves, so we will just set this to an empty string. Here's the code.

?
int EnumFixedFonts(void)
{
    LOGFONT logfont;
    ZeroMemory(&logfont, sizeof logfont);
    logfont.lfCharSet = DEFAULT_CHARSET;
    logfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
    lstrcpy(logfont.lfFaceName, "\0");
    hdc = GetDC(0);
    EnumFontFamiliesEx(hdc, &logfont, (FONTENUMPROC)FontNameProc, 0, 0);
    ReleaseDC(0, hdc);
    return 0;
}

Before you can use this call though, you must write a callback function which Windows will call during the font enumeration. This callback procedure is called once for every font windows finds. Bear in mind that this procedure could be called several times for the same font, once for every character set. This is what the callback function should look like.

?
int CALLBACK FontNameProc(
    ENUMLOGFONTEX    *lpelfe,   /* pointer to logical-font data */
    NEWTEXTMETRICEX  *lpntme,   /* pointer to physical-font data */
    int FontType,  /* type of font */
    LPARAM lParam     /* a combo box HWND */
    )
{
    int i;
    if(lpelfe->elfLogFont.lfPitchAndFamily & FIXED_PITCH)
    {
        /* Make sure the fonts are only added once */
        for(i = 0; i < curfont; i++)
        {
            if(lstrcmp(currentfonts[i], (char *)lpelfe->elfFullName) == 0)
                return 1;
        }
        printf("%-16s: ", lpelfe->elfFullName);
        cursize = 0;
        EnumFontSizes((char *)lpelfe->elfFullName);
        printf("\n");
        lstrcpy(currentfonts[curfont], (char *)lpelfe->elfFullName);
        if(++curfont == 200)
            return 0;
    }
    return 1;
}

An important part of the callback function is to check if the font has already been enumerated. In this case we keep a list of all fonts that have been enumerated so far. If the callback function executes and the font is already in the list of "processed fonts", then the function just returns 1 (or any non-zero value) to continue to the next font.

Now that we have a method to find every fixed-width font, we can enumerate all of the font sizes for each font.

Enumerate font sizes

Font size enumeration is almost the same as enumerating the fonts themselves. The only difference is that we specify the name of each font to get the sizes for. The following function enumerates all available font sizes for the specified font name

?
int EnumFontSizes(char *fontname)
{
    LOGFONT logfont;
    ZeroMemory(&logfont, sizeof logfont);
    logfont.lfHeight = 0;
    logfont.lfCharSet = DEFAULT_CHARSET;
    logfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
    lstrcpy(logfont.lfFaceName, fontname);
    EnumFontFamiliesEx(hdc, &logfont, (FONTENUMPROC)FontSizesProc, 0, 0);
    return 0;
}

By looking at the font name callback listed previously, you will see that this font size enumeration is executed for every unique font that we find. This is purely for the purpose of this example, as I wanted to display the available font sizes along side each font. In a normal Windows application, you would probably want to just store the font names in a drop-down list box, and whenever a font was selected from this list, fill up another list box with its available sizes.

?
int CALLBACK FontSizesProc(
    LOGFONT *plf,      /* pointer to logical-font data */
    TEXTMETRIC *ptm,   /* pointer to physical-font data */
    DWORD FontType,    /* font type */
    LPARAM lParam      /* pointer to application-defined data */
    )
{
    static int truetypesize[] = { 8, 9, 10, 11, 12, 14, 16, 18, 20,
            22, 24, 26, 28, 36, 48, 72 };
    int i;
    if(FontType != TRUETYPE_FONTTYPE)
    {
        int logsize    = ptm->tmHeight - ptm->tmInternalLeading;
        long pointsize  = MulDiv(logsize, 72, GetDeviceCaps(hdc, LOGPIXELSY));
        for(i = 0; i < cursize; i++)
            if(currentsizes[i] == pointsize)
                return 1;
        printf("%d ", pointsize);
        currentsizes[cursize] = pointsize;
        if(++cursize == 200) return 0;
        return 1;
    }
    else
    {
        for(i = 0; i < (sizeof(truetypesize) / sizeof(truetypesize[0])); i++)
        {
            printf("%d ", truetypesize[i]);
        }
        return 0;
    }
}

An important thing to note for the font-size callback function (above) is the test to see if the font is true-type or not. If it is, then all that is necessary is to print a list of pre-determined point sizes which are deamed suitable. In this case, the callback will only be executed once for a true-type font. However, if the font is a raster font, then the callback will be executed once for every size that the font supports. It is our job to convert the size of the font from logical units into point sizes, which are generally more useful if they are to be presented to a user.

Thats it. All that is needed now is to start off the enumeration!

?
int main(void)
{
    EnumFixedFonts();
    return 0;
}

Below is the output of the program on my system, which currently has six fixed-width fonts installed. Notice that three of the fonts are obviously raster fonts, and have a limited number of sizes, whereas the Courier New, Lucida and Andale fonts are true-type.

?
Terminal        : 9 5 6 14 12
Fixedsys        : 9
Courier         : 10 12 15
Courier New     : 8 9 10 11 12 14 16 18 20 22 24 26 28 36 48 72
Lucida Console  : 8 9 10 11 12 14 16 18 20 22 24 26 28 36 48 72
Andale Mono     : 8 9 10 11 12 14 16 18 20 22 24 26 28 36 48 72

22 May 2001

Posted in Tutorials

win32
Powered by Drupal


Customize date-time controls | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
datetime.zip

Published on 22 Jan 2012

4.18 Kb

Download

Customize date-time controls

Improve the apearance of the date-time control

This is quite a short tutorial. The purpose of this exercise to remove the spin-buttons from the edge of a date-time control. The result is a control with looks like an edit box, but behaves exactly like a date-time control. This picture will show you the effect.

The standard date-time control looks rather messy in my opinion, especially when several are clustored together like in the example shown above.

With a little subclassing we can make the date-time controls look much cleaner in appearance. The second dialog looks far less cluttered, and isn't so overwhelming to look at. The new looking date-time control pictured above is achieved using two steps.

Hiding the Spin control

This is achieved by resizing the control horizontally by an amount large enough so that the spin control moves outside of the original bounding rectangle of the control. This happens because the date-time control reacts to the WM_SIZEmessage, and when it is resized, it aligns the spin control to the right-hand side of the control.

Next the control is subclassed. The subclass procedure intercepts the WM_SIZE message so that it performs no processing when that message is received. We then size the control back to its original position. Because the control no longer performs any WM_SIZE processing, the spin child control is not aligned to the right-hand side any more, and becomes hidden because it is clipped outside the control's window rectangle.

?
int MakeCoolDateTime(HWND hwndDT)
{
    RECT    rect;
    WNDPROC oldproc;

    //work out how big the date-time control is.
    GetWindowRect(hwndDT, &rect);
    OffsetRect(&rect, -rect.left, -rect.top);
    //resize the date-time control horizontally. We don't
    //want to move it, so we can use SetWindowPos.
    SetWindowPos(hwndDT, 0, 0, 0, rect.right+100, rect.bottom,
        SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER);
    //subclass the control so when we size it back to what it was,
    //our wndproc will intersept the WM_SIZE, and the scrollbar will not
    //get shifted back into the control
    oldproc = (WNDPROC)SetWindowLong(hwndDT, GWL_WNDPROC, (LONG)NewDTProc);
    //we need to remember the old window procedure. Just store
    //the pointer in the USERDATA area of the window.
    SetWindowLong(hwndDT, GWL_USERDATA, (LONG)oldproc);
    //make it a little smaller too
    rect.right -= 2;
    rect.bottom -= 2;
    //size the date-time control back to it's original place
    SetWindowPos(hwndDT, 0, 0, 0, rect.right, rect.bottom,
        SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER);
    return 0;
}

Creating a flat look

The flat look is achieved by handling the WM_NCPAINT message in the subclass procedure. Instead of passing control to the old window procedure for the date-time control, we simply draw our own border using a single grey line.

?
case WM_NCPAINT:
    hdc = GetWindowDC(hwnd);
    //work out the coordinates of the window rectangle,
    GetWindowRect(hwnd, &rect);
    //make them relative to the top-left of the window
    OffsetRect(&rect, -rect.left, -rect.top);
    InflateRect(&rect, -1, -1);
    //Draw a single line around the outside
    FrameRect(hdc, &rect, GetSysColorBrush(COLOR_3DSHADOW));
    ReleaseDC(hwnd, hdc);

    return 0;

To give the control a different coloured back-ground, we could probably use the Custom Draw facility provided by the Date-Time control. However, it is much easier to handle the WM_ERASEBKGND in this situation, because we already have a subclass procedure in place. All we do is fill the background using our own colour instead of letting the default window procedure use the standard window background.

?
case WM_ERASEBKGND:
    hdc = (HDC)wParam;
    GetClientRect(hwnd, &rect);
    FillRect(hdc, &rect, GetSysColorBrush(COLOR_BTNHILIGHT));
    return -1;

Conclusion

You can see that by just using a simple subclassing procedure we have dramatically changed the appearance of a control. There are many enhancements that we could add to the date-time control by just adding a little more logic to the control. One possiblity would be to only show the spin control when the mouse moves over a date-time control. This would require mouse hit-testing, and extending the WM_SIZE logic.

23 Aug 2001

Posted in Tutorials

win32
Powered by Drupal


Drive-list control | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
drivelist.zip

Published on 22 Jan 2012

8.51 Kb

Download

Drive-list control

A drop-down list displaying drive names and icons

This tutorial will show you how to implement a drop-down list of all the logical drives installed on a Windows system. There are basically three major steps involved in achieving this goal, listed in the order that we will tackle them.

  • Enumerating the disk drives on the current system.
  • Obtaining the correct icon images used to draw the drives.
  • Handling the WM_DRAWITEM message to draw the drop-down list.

Enumerating disk drives

This one is really easy. All we do is call the GetLogicalDriveStrings API call to retrieve the complete list of all logical drives currently installed.

The returned buffer contains a number of zero-terminated strings, one after the other. The buffer is terminated by an additional null character, to mark the end of the drive names. The code below demonstates how to retrieive the list of drive names, and extract each name in turn from the string buffer.

?
char szDrives[1024];
char szText[32];
int drivelen;
drivelen = GetLogicalDriveStrings(1023, szDrives);while(drivelen != 0)
{
    int drivetype, len;
    len = lstrlen(driveptr) + 1; //plus the NULL
    drivelen -= len;
    //convert drive name to lower case
    driveptr = strlwr(driveptr);
    lstrcpyn(szText, driveptr, 3);  //only copy the 'X:' portion
    //retrieve the index of the icon in the system image list
    //add the drive name to the combo box
    driveptr += len;
}

Getting the icon image

This is the easiest job of all. We don't need to draw our own icons, because we can use the icons that the Windows shell uses to display drives. These system icons are located in a resource called the System Image List. So, we need to obtain the HIMAGELIST handle to this image list, and the index into this list of the icon image we want. Thankfully there is a function which obtains an index this for us, called ShGetFileInfo.

?
SHFILEINFO shfi;
HIMAGELIST hSysImgList;
//retrieve the index of the icon in the system image list
hSysImgList = (HIMAGELIST)SHGetFileInfo("C:", 0, &shfi, sizeof(SHFILEINFO),
                                          SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_ICON);

The snippet above retrieves a handle to the system image list, and also the index of the icon associated with the "C:" drive. The icon index is stored in the shfi.iIcon varaible when the ShGetFileInfo function returns.

Handling the WM_DRAWITEM message

It is also pretty straight-forward to draw the icons along side each drive name. We do this using the standard Owner-Draw feature that a Combo box supports. Owner-Draw is enabled for a combo-box by creating the control with the CBS_OWNERDRAWFIXED style.

?
BOOL DriveListMeasure(HWND hwnd, UINT uCtrlId, MEASUREITEMSTRUCT *mis)
{
    HFONT hFont = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0);
    mis->itemHeight = DriveList_GetLineHeight(hwnd, hFont, nBitmapHeight);
    //(16)
    nBitmapYOff = (mis->itemHeight - (nBitmapHeight-1)) / 2;
    return TRUE;
}
?
BOOL DriveListDraw(HWND hwnd, UINT uCtrlId, DRAWITEMSTRUCT *dis)
{
    HWND hwndCombo = GetDlgItem(hwnd, uCtrlId);
    char szText[MAX_PATH+1];
    char *ptr;
    int idx;
    switch(dis->itemAction)
    {
    case ODA_FOCUS:
        if(!(dis->itemState & ODS_NOFOCUSRECT))
            DrawFocusRect(dis->hDC, &dis->rcItem);
        break;
    case ODA_SELECT:
    case ODA_DRAWENTIRE:
        SendMessage(hwndCombo, CB_GETLBTEXT, dis->itemID, (LONG)szText);
        if(dis->itemState & ODS_SELECTED)
        {
            SetTextColor(dis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
            SetBkColor(dis->hDC, GetSysColor(COLOR_HIGHLIGHT));
        }
        else
        {
            SetTextColor(dis->hDC, GetSysColor(COLOR_WINDOWTEXT));
            SetBkColor(dis->hDC, GetSysColor(COLOR_WINDOW));
        }
        // we set the SB_SETITEMDATA DWORD to be the system image list index
        // for this drive icon. Retrieve the value so we know which picture
        // to display for this drive
        idx = dis->itemData;

        ptr = strchr(szText, ':');
        if(ptr) ptr[1] = '\0';
        //Draw the drive letter
        ExtTextOut(dis->hDC, dis->rcItem.left+22, dis->rcItem.top+1,
                   ETO_OPAQUE, &dis->rcItem, szText, lstrlen(szText), 0);
        //Draw the drive volume name
        if(ptr)
            ExtTextOut(dis->hDC, dis->rcItem.left+22+16,
             dis->rcItem.top+1, 0, 0, ptr+1, lstrlen(ptr+1), 0);
        if(dis->itemState & ODS_FOCUS)
        {
            if(!(dis->itemState & ODS_NOFOCUSRECT))
                DrawFocusRect(dis->hDC, &dis->rcItem);
        }
        //Draw the drive icon over the top of the selection
        ImageList_Draw(hSysImgList, idx, dis->hDC,
            dis->rcItem.left + 2,
            dis->rcItem.top + nBitmapYOff,
            ILD_TRANSPARENT);
        break;
    }
    return TRUE;
}

Conclusion

That's all there is to do for now. I've omitted a few details for clarity's sake, but the source code download shows how to do this in full.

This implementation of a drive-list uses the standard Windows combo box to display the drives. The ComboBoxEx control which is available in the Common Controls Library could have been used instead. This new style combo box has automatic support for item images through an image list. There were two reasons I chose not to use this new control. The first is that the ComboBoxEx is not available under some versions of Windows (those which have an early version of the common controls library). The second reason is that I wanted to achieve the same look and feel as a "normal" drive list control, so the standard Combo box was chosen.

25 Aug 2001

Posted in Tutorials

win32
Powered by Drupal


Directory list control | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
dirlist.zip

Published on 22 Jan 2012

7.83 Kb

Download

Directory list control

A directory-picker list box

Does anyone else miss the old style Windows 3.1 directory list boxes? I find the new Windows 95 directory tree view controls pretty cumbersome to navigate around. They display far too much information at any one time, and as a result it can be confusing to navigate around the control to locate a folder.

The original style controls were declared obsolete because they didn't do a very good job of displaying long file names. The beauty of them was though, that they displayed only the necessary information to describe to the user what the current directory was, and what sub-directories were available. If you look at the picture above, you can instantly tell that the current directory is G:\WINNT\system32, because of the intuitive use of a hierarchy which displays sub-directories at increasing levels of indentation. A subtle but effective way to communicate a complex idea.

The directory list control that I have implemented solves the long filename problem by enabling the horizontal scrollbar in the control when necessary, i.e. when a filename becomes too big to display in the list without scrolling. Because the full source code is available, you can make the list control any size you wish as well, so you can make it big enough to display typical long filenames.

Getting Started

Implementing a directory list control is pretty easy once you know how. The process can be broken down into two major steps:

  • Building the list control
  • Drawing the list control

The key to the implementation is separating the logic needed to draw the list from the logic used to build the list contents. We are going to use the owner-draw facility provided by a list box control to customize the appearence of the list. Owner-draw operates on an item-based manner. So, we need to know how to draw each item in the list, without having to work out the relationship between items each time we have to draw them.

Look the image of a directory list once more. You will see that each item in the list posesses two properties that makes it unique. Each item's folder icon is in one of two states: either opened (1) or closed (0). In addition, each item has a hierarchy in the list. The first item has a "depth" of 0, the second item a depth of 1, and so on.

The image on the right identifies the two properties that each list item requires. The first number is the item depth, or hierarchy level. The second value is the state of the folder image. You should be able to see the relationship between the item properties and what they represent.

You also should notice how the directory components appear in the list. The actual directory that is displayed (in this case G:\WINNT\system32) is split into its component directory names ("G:\", "WINNT" and "system32"). Each component uses the "opened" icon. Each component also has an increasing depth (starting with 0).

The subdirectories under G:\WINNT\system32 always appear after the "current directory". They all have the same depth, or hierarchy. In addition, all sub-directories use the "closed" icon to indicate that they havn't been opened yet.

Associating properties with each list item

Now that we know we need two properties per list item, we need to set these two properties somehow. There is an easy solution: we will use the LB_SETITEMDATA message to set the 32bit integer value that each list item has. Because we need to set two values, we can use the low 16bits to set one value, and the high 16bits to set the second value. The following function associates two properties with one list item.

?
void SetItemState(HWND hwndList, int idx, int level, int state)
{
    SendMessage(hwndList, LB_SETITEMDATA, idx, MAKELONG(level, state));
}

This is a simple way to associate value(s) with a list item. However, if you needed to associate more complicated data with a list item then the best solution would be to allocate a structure containing the properties. You would then set the item data to be a pointer to one of these structures. Our simple solution is fine for this purpose however.

Building the directory list control

The first step is to fill the list box with the component names of the directory that you want to browse. For example, assuming that you want to browse the G:\WINNT\system32 directory:

  1. First, the components that make up the path "G:\WINNT\system32" must be added to the start of the list. So, the strings "G:\", "WINNT" and "system32" are added first of all. These strings are added with the "opened" icon state.
  2. Second, any sub-directories under the "G:\WINNT\system32" are added after the path components. These sub directory names can be obtained using the FindFirstFile / FindNextFile API calls. Every name is added with the "closed" icon state, and all with a constant depth.

After these two steps have been performed, the list-box will look something like this:

Here is an example of what the code might look like to perform this initial step.

?
void DirList_AddItem(HWND hwndList, char *szSubDir, int level, int state)
{
    int idx;
    idx = SendMessage(hwndList, LB_INSERTSTRING, -1, (LPARAM)szSubDir);
    SetItemState(hwndList, idx, level, state);
}
?
//
// This function adds the names of all sub-directories
// under the path specified by szPath, into the list identified
// by hwndList
//
void DirList_AddSubDirs(HWND hwndList, char *szPath)
{
    HANDLE hFind;
    WIN32_FIND_DATA win32fd;
    char szSearchPath[_MAX_PATH];

    lstrcpy(szSearchPath, szPath);

    if(szPath[lstrlen(szPath) - 1] != '\\')
        lstrcat(szSearchPath, "\\");
    lstrcat(szSearchPath, "*.*");
    if((hFind = FindFirstFile(szSearchPath, &win32fd)) == INVALID_HANDLE_VALUE)
        return;
    do
    {
        if(win32fd.cFileName[0] != '.' &&
          (win32fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
         !(win32fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
        {
            DirList_AddItem(hwndList, win32fd.cFileName, level, FOLDER_CLOSED);
        }
    } while(FindNextFile(hFind, &win32fd) != 0);
    FindClose(hFind);
}
?
//
// This function fills the specified list box control
// with the specified path name
//
BOOL DirList_SetPath(HWND hwndList, char *szPath)
{
    char szFullPath[MAX_PATH];
    char szText[MAX_PATH];
    int items = 0;
    char *szName;
    char *szSep = szFullPath;
    GetFullPathName(szPath, MAX_PATH, szFullPath, &szName);
    SendMessage(hwndList, LB_RESETCONTENT, 0, 0);
    szSep = strchr(szSep, '\\');
    if(!szSep) lstrcpy(szText, szFullPath);
    else lstrcpyn(szText, szFullPath, szSep-szFullPath+2);
    DirList_AddItem(hwndList, szText, items++, FOLDER_OPENED);
    while(szSep && *(szSep+1) != '\0')
    {
        char *szOld = szSep;
        szSep = strchr(szSep+1, '\\');
        if(!szSep) lstrcpy(szText, szOld+1);
        else lstrcpyn(szText, szOld+1, szSep-szOld);
        DirList_AddItem(hwndList, szText, items++, FOLDER_OPENED);
    }
    //Add the sub-directories
    DirList_AddSubDirs(hwndList, szFullPath, items);
    SendMessage(hwndList, LB_SETCURSEL, items-1, 0);
    return TRUE;
}

Drawing the list control

We are going to use Owner-draw to display this directory list. To enable owner-draw for a list-box, we have to set the LB_OWNERDRAWFIXED style. Once this style is set, the parent of the list control will receive a WM_DRAWITEM message whenever an individual item needs to be painted.

The only thing left to do is obtain a couple of images for the folder states. The best place to find these is inside the System Image List. This image list is just a standard HIMAGELIST, as found in the common controls library. We get a handle to the system image list using the ShGetFileInfo function.

?
SHFILEINFO shfi;
HIMAGELIST hSysImgList;
//retrieve the index of the icon in the system image list
hSysImgList = (HIMAGELIST)SHGetFileInfo("C:", 0, &shfi, sizeof(SHFILEINFO),
                                          SHGFI_SMALLICON | SHGFI_SYSICONINDEX | SHGFI_ICON);

The advantage of the system image list is two-fold. Firstly, the list is made up of icon images. This is good because it is easy to draw icons with a transparent back-ground. The second advantage is that the folder images will look correct on whatever version of Windows that you run on.

?
BOOL DirListDraw(HWND hwnd, UINT uCtrlId, DRAWITEMSTRUCT *dis)
{
    HWND hwndCombo = GetDlgItem(hwnd, uCtrlId);
    char szText[MAX_PATH];
    int idx;
    int nState, nLevel;

    switch(dis->itemAction)
    {
    case ODA_FOCUS:

        //Windows 2000 doesn't display a control's focus in
        //a dialog box until the ALT key is pressed.
        if(!(dis->itemState & ODS_NOFOCUSRECT))
            DrawFocusRect(dis->hDC, &dis->rcItem);

        break;
    case ODA_SELECT:
    case ODA_DRAWENTIRE:
        // get the text string to display, and the item state.
        SendMessage(hwndCombo, LB_GETTEXT, dis->itemID, (LONG)szText);
        GetItemState(hwndCombo, dis->itemID, &nLevel, &nState);
        if(dis->itemState & ODS_SELECTED)
        {
            SetTextColor(dis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
            SetBkColor(dis->hDC, GetSysColor(COLOR_HIGHLIGHT));
        }
        else
        {
            SetTextColor(dis->hDC, GetSysColor(COLOR_WINDOWTEXT));
            SetBkColor(dis->hDC, GetSysColor(COLOR_WINDOW));
        }
        //draw the item text first of all. The ExtTextOut function also
        //lets us draw a rectangle under the text, so we use this facility
        //to draw the whole line at once.
        ExtTextOut(dis->hDC,
            dis->rcItem.left + nBitmapXSpace + nBitmapWidth + nLevel * CX_OFFSET,
            dis->rcItem.top + 1,
            ETO_OPAQUE, &dis->rcItem, szText, lstrlen(szText), 0);
        idx = nState == FOLDER_OPENED ? nImgFolderOpened : nImgFolderClosed;
        //draw the folder image, using an icon from an image list.
        ImageList_Draw(hDirImgList, idx, dis->hDC,
            dis->rcItem.left + 2 + nLevel * CX_OFFSET,
            dis->rcItem.top + nBitmapYOff,
            ILD_TRANSPARENT);
        break;
    }
    return TRUE;
}

Centering the folder image

If you actually looked at the source above, you will see that there is a variable called nBitmapYOff. This variable is the distance in pixels that the folder image needs to be offset by so that it is centered in the list item. When a large system font is being used, the text of a list box will be much larger than the height of the folder bitmap, so the folder image is centered to create a more pleasing effect. A simple formula is used to calculate the nBitmapYOff variable.

?
nBitmapYOff = (nLineHeight - (nBitmapHeight - 1)) / 2;

The nLineHeight value is the size in pixels of each line in the list. This is calculated whenever a WM_MEASUREITEM message is received. This message instructs the parent of the directory list to set the size in pixels of each item in the list. The code below calculates this amount by taking the maximum value between the folder bitmap height and the height of a line of text.

?
TEXTMETRIC      tm;
HANDLE hOldFont = NULL;
HDC hdc;
int nTextHeight;
int nLineHeight;
hdc = GetDC(hwndCombo);
hOldFont = SelectObject(hdc, hFont);
GetTextMetrics(hdc, &tm);
SelectObject(hdc, hOldFont);
ReleaseDC(hwndCombo, hdc);
nTextHeight = tm.tmHeight + tm.tmInternalLeading;
nLineHeight = max(nBitmapHeight, nTextHeight);

Adding horizontal scrollbar support

To make sure that extra-long directory names are still visible in our list, we need to enable the horizontal scrollbar on the list control. There is a list-box message called LB_SETHORIZONTALEXTENT which is used to set the amount by which the list-box scrolls horizontally. Without this message, no horizontal scrollbar will be shown.

LB_SETHORIZONTALEXTENT requires one parameter - the size in pixels of the area to scroll horizontally. Therefore we need to calculate the width of the longest entry in the directory list, and use this value when we send the message.

The easiest way to calculate the length of the longest line is to use a seperate function, which loops through all of the items in the list. The length of each line is calculated using the GetTextExtentPoint32 function. Each item's hierarchy in the list is taken into account, and the appropriate distance added to the line length.

?
void DirList_UpdateWidth(HWND hwndList)
{
    HANDLE hOld;
    HDC hdc;
    HFONT hFont;
    RECT rect;
    int i, count, width = 0;
    int nLevel, nState;
    hdc   = GetDC(hwndList);
    hFont = (HFONT)SendMessage(hwndList, WM_GETFONT, 0, 0);
    hOld = SelectObject(hdc, hFont);
    count = SendMessage(hwndList, LB_GETCOUNT, 0, 0);
    // loop over each line in the directory list
    for(i = 0; i < count; i++)
    {
        SIZE sz;
        char ach[MAX_PATH];
        // retrieve this item's text
        SendMessage(hwndList, LB_GETTEXT, i, (LONG)ach);
        GetItemState(hwndList, i, &nLevel, &nState);
        // calculate the size of the text
        GetTextExtentPoint32(hdc, ach, lstrlen(ach), &sz);
        // add on the size of the folder image, and the amount
        // that this item is indented by
        sz.cx += 4 * GetSystemMetrics(SM_CXBORDER) +
            nBitmapXSpace + nBitmapWidth + nLevel * CX_OFFSET;
        if(sz.cx > width)
            width = sz.cx;
    }
    GetClientRect(hwndList, &rect);
    // if the horizontal scrollbar is not needed, the hide it
    if(width < rect.right) ShowScrollBar(hwndList, SB_HORZ, FALSE);
    else ShowScrollBar(hwndList, SB_HORZ, TRUE);
    // set the horizontal scroll amount
    SendMessage(hwndList, LB_SETHORIZONTALEXTENT, width, 0);
    SelectObject(hdc, hOld);
    ReleaseDC(hwndList, hdc);
}

Conclusion

That's basically all that needs to be done to implement a directory list control. There are a few more little bits and pieces to get it to operate correctly. The supplied source code provides a fully implemented directory list, so you can look there to see the other details.

The technique presented here can also be used to draw pretty almost any list-based hierarchy. Just remember to separate the implementation into two stages: building the list and drawing the list. If you stick to this method it is much easier to draw hierarchies in this way.

26 Aug 2001

Posted in Tutorials

win32
Powered by Drupal


Custom Titlebar | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
titlebar.zip

Published on 22 Jan 2012

8.55 Kb

Download

Custom Titlebar

How to insert buttons into a window's caption area

This article will teach you the techniques used to insert one or more buttons into the title bar (caption area) of any window. The source code download implements a complete library which supports all types of window, including standard frame windows, and tool windows with smaller captions.

Important

Note that this article was originally written in 20001 - it is only relevant for Windows 95/98/ME and Windows NT/2000/XP. Customizing the titlebar in Vista and Windows 7 (using the Aero theme) requires overlaying windows on top of the titlebar.

The techniques presented here are fairly straight-forward. The same techniques can be applied to perform any title bar customization, although this article will concentrate just on inserting buttons next to the standard minimize, maximize and close caption buttons.

Subclassing the window

As with most other window customizations, this method will subclass the window in question in order to provide the additional functionality.

Every inserted button is going to need some form of state. The structure definition below describes the necessary attributes for a button.

?
typedef struct
{
    UINT uCmd;          //Command to send when clicked (WM_COMMAND)
    int nRightBorder;  //Pixels between this button and buttons to the right
    HBITMAP hBmp;          //Bitmap to display
    BOOL fPressed;      //Is the button pressed in or out?.
} CaptionButton;

Whilst this is sufficient to describe one button, there is still some additional information we need to keep track of. We need to know the number of buttons that are inserted, an array of these buttons, and somewhere to store the window's old window procedure. The following structure contains these details, and it is this structure that we associate with the subclassed window.

?
typedef struct
{
    CaptionButton  buttons[MAX_TITLE_BUTTONS];
    int nNumButtons;
    BOOL fMouseDown;        // is the mouse button being clicked?
    WNDPROC        wpOldProc;         // old window procedure
    int iActiveButton;     // the button index being clicked.
} CustomCaption;

We're now ready to implement an InsertButton function. This function will perform a number of steps to insert a button into a window's titlebar.

  1. Allocate memory for a CustomCaption structure if this is the first button being inserted.
  2. Associate this structure with the window (using SetProp API, if necessary)
  3. Replace the window procedure with our custom one.
  4. Fill in one entry in the CaptionButton array to represent a button.

Note that there aren't any physical changes to the window when we insert a button. Unlike the edit-control button tutorial, where space was allocated for a button in the non-client area using WM_NCCALCSIZE, in this case there is no need. This is because the caption bar already exists, and we will be drawing our buttons over the top of that.

?
BOOL Caption_InsertButton(HWND hwnd, UINT uCmd, int nBorder, HBITMAP hBmp);

Calculating the button position

Our custom titlebar buttons don't have a fixed position or size. Their location and size are completely dependent on the current size of the window, and the current system setting for the titlebar height. It will therefore be necessary to write a function which calculates the current location of a button at any given time. This location is required when we come to draw the buttons, and also when we come to process mouse messages for them.

The function will look like this:

?
void GetButtonRect(CustomCaption *ctp, HWND hwnd, int idx, RECT *rect, BOOL fWindowRelative);

We need to pass a pointer to the CustomCaption structure for a given window, the window handle itself, the index of the button we want the location for and a pointer to a RECT structure to receive the coordinates. The last parameter, fWindowRelative, controls what the returned coordinates will be relative to. A value of TRUE results in window-relative coordintes (from the top-left of the window). A value of FALSE results in screen coordinates.

The GetButtonRect function needs to take a few things into consideration in order to return a correct button location.

  1. Titlebar button size (in pixels).This information can be retrieved using the GetSystemMetrics API call, but we need to test if the window is a standard window or a tool-window (with a narrow titlebar).
  2. Current caption buttons.A window can contain four different caption buttons. These are the Close [x], Minimize [-], Maximize [+] and Context Help [?] buttons. If we want to preserve these buttons then we need to make sure that our inserted buttons never overlap any existing buttons. The GetButtonRect function tests the window styles to determine how many buttons are already in place.
  3. Window border size.A window can have many different border sizes, depending on if it has a resizing or fixed border, but also on current system settings. GetButtonRect calculates the current border size and takes these dimensions into account.
  4. Previously inserted buttons.The GetButtonRect function also needs to take into account any buttons that were inserted before the one we are interested in, and calculate not only their sizes (based on the system settings), but also the border spacing setting that our buttons have.

Now that we can precisely locate any one of our custom buttons, we can proceed onto the next step: actually drawing the buttons.

Drawing the inserted buttons

The custom buttons live in the non-client area of a window (more precisely, the caption bar). Therefore this requires us to handle the WM_NCPAINT message.

Drawing the custom buttons is going to be a little more difficult than usual. We ideally want to keep the actual drawing to a minimum. If possible, we want the default window procedure to paint as much as possible before we step in and perform our customization. The problem is that we have to preserve the existing window title bar whilst drawing our own buttons on top. And we have to make sure that there is no flickering or painting glitches.

The basic strategy will be to get the original window procedure to draw the whole window frame and caption, but with the button areas masked out. Once the window has been painted, all that is left to do is to paint each button in turn into the masked-out areas. I will break the process down into steps so that you can see how this is achieved.

The first step is to build up a region which describes the whole window, minus the areas our buttons will take. This is achieved with the CreateRectRgn API call to obtain the whole window region. The button areas are then masked out with calls to the CombineRgn API call, specifying the RGN_XOR flag to "cut" button-shaped rectangles out of the window region.

One very important thing to mention is that all regions and rectangles must be in screen coordinates. The WM_NCPAINT handler will look something like this.

?
HRGN hrgn, temprgn;
// Get the SCREEN coordinates of this window.
GetWindowRect(hwnd, &rect);
// Create a region which covers the whole window.
if(wParam == 1)
    hrgn = CreateRectRgnIndirect(&rect);
else
    hrgn = (HRGN)wParam;
// Clip our custom buttons out of the way...
for(i = 0; i < ctp->nNumButtons; i++)
{
    //Get button rectangle in screen coords
    GetButtonRect(ctp, hwnd, i, &rect, FALSE);
    temprgn = CreateRectRgnIndirect(&rect);
    //Cut out a button-shaped hole
    CombineRgn(hrgn, hrgn, temprgn, RGN_XOR);
    DeleteObject(temprgn);
}

That's the tricky part out of the way. The next task is to get the original window procedure to paint the entire window frame and caption (like it would do normally).

The wParam argument to WM_NCPAINT is a handle to an update region. MSDN states that when wParam is 1, this indicates that the entire non-client area is to be updated. Our paint handler needs to test for this occurance and create a valid window region when this happens. Otherwise, wParam already represents a valid update region, so we can use that instead when we mask our buttons out.

Now, when we call the original window procedure, instead of passing the original handle specified by wParam, we can pass in our modified update region. It works like a charm. The window is drawn normally, but with our inserted buttons masked out of the way.

?
CallWindowProc(ctp->wpOldProc, hwnd, WM_NCPAINT, (WPARAM)hrgn, 0);

All that is left is to draw our buttons! To do this we need to obtain a device context to the window, using the GetWindowDC API call. Next, the buttons are drawn in a simple for-loop. Another important note here. When we draw into a window device-context, all coordinates must now be window relative. That is, relative to the top-left of the window.

The code below just draws a blank button, either pressed or normal:

?
HDC hdc = GetWindowDC(hwnd);
// Draw buttons in a loop
for(i = 0; i < ctp->nNumButtons; i++)
{
    //Get Button rect in window coords
    GetButtonRect(ctp, hwnd, i, &rect1, TRUE);

    if(ctp->buttons[i].fPressed)
        DrawFrameControl(hdc, &rect1, DFC_BUTTON, DFCS_BUTTONPUSH | DFCS_PUSHED);
    else
        DrawFrameControl(hdc, &rect1, DFC_BUTTON, DFCS_BUTTONPUSH);
   //Draw the bitmap on top...
}
ReleaseDC(hwnd, hdc);
if(wParam == 1)
    DeleteObject(hrgn);

We have to be careful to release the update region if we allocated it ourselves. If wParam was a valid region, then we don't need to because Windows owns the update region. The code download also draws a bitmap into the button, but I wanted to keep the code as simple as possible here.

Making the buttons work.

What we have so far is a window with extra buttons drawn into the title bar. They don't actually do anything yet - so far, they're just decoration. It's pretty simple to make the buttons behave just like the standard caption buttons. In fact, the process is almost exactly the same as the one I described in another article (Inserting a button into an edit control).

Mouse button down (WM_NCLBUTTONDOWN)

When the left mouse button is clicked, and the mouse cursor is within one of the areas occupied by our buttons, then we need to react to this event and give the button a depressed look. We will also set the mouse capture so we can receive all further mouse messages whilst the mouse button is held down. This means that we can move the mouse outside the window and can still retrieve mouse messages.

The code below checks each inserted button in turn to see if the mouse is inside one as the mouse button is clicked. If a positive intersection is found then the mouse capture is set, and a variable set (iActiveButton) to record what the active button is. The caption is redrawn to reflect the button in a "pushed" state.

?
int i;
RECT rect;
POINT pt;
// retrieve the mouse coordinates
pt.x = (short)LOWORD(lParam);
pt.y = (short)HIWORD(lParam);
// Loop over all buttons to see if one has been clicked
for(i = 0; i < ctp->nNumButtons; i++)
{
    //get screen coordinates of each button
    GetButtonRect(ctp, hwnd, i, &rect, FALSE);
    InflateRect(&rect, 0, 2);

    //if clicked in a custom button
    if(PtInRect(&rect, pt))
    {
        ctp->iActiveButton = i;
        ctp->buttons[i].fPressed = TRUE;
        ctp->fMouseDown = TRUE;

        SetCapture(hwnd);

        RedrawCaption(hwnd);

        return 0;
    }
}
return CallWindowProc(ctp->wpOldProc, hwnd, msg, wParam, lParam);

Mouse Movement (WM_MOUSEMOVE)

The WM_MOUSEMOVE message will be received whenever the mouse moves within the client area of a window, but will be also be received outside the client area when the mouse capture is set. The mouse-move handler is very similar to the left-click handler shown above. The only difference is a small amount of code to convert the client-relative mouse coordinates to screen-relative coordinates (a simple ClientToScreen call). The other change is a test to see if the mouse is inside or outside the active button's region. If it is inside, then the button is drawn with a sunken appearance. When the mouse is outside the active button, then the button is drawn normally. This behaviour causes the button to be pushed in and out as the mouse moves over it.

?
GetButtonRect(ctp, hwnd, ctp->iActiveButton, &rect, FALSE);
if(PtInRect(&rect, pt))
    ctp->buttons[ctp->iActiveButton].fPressed = TRUE;
else
    ctp->buttons[ctp->iActiveButton].fPressed = FALSE;

Mouse button up (WM_LBUTTONUP)

The left-button-up handler is also very similar to the mouse-move handler. The only difference being the button is restored to its non-pressed state, and a WM_COMMAND message is sent to the window if the mouse is still within the active button when the mouse button is released. This WM_COMMAND basically simulates a standard button press - the command identifier is stored in wParam.

?
GetButtonRect(ctp, hwnd, ctp->iActiveButton, &rect, FALSE);
if(PtInRect(&rect, pt))
    SendMessage(hwnd, WM_COMMAND, ctp->buttons[ctp->iActiveButton].uCmd, 0);

Hit-testing (WM_NCHITTEST)

Last one! This message handler must check if the mouse is inside any one of the inserted buttons. Because our inserted buttons are inside the caption area, the default handler for this message will return HTCAPTION. We need to override this, so we will return HTBORDER instead to prevent any caption-related behaviour occuring when the user clicks our button.

?
for(i = 0; i < ctp->nNumButtons; i++)
{
    GetButtonRect(ctp, hwnd, i, &rect, FALSE);
    if(PtInRect(&rect, pt))
        return HTBORDER;
}
return CallWindowProc(ctp->wpOldProc, hwnd, msg, wParam, lParam);

One last step

We're almost there! We now have fully functional buttons inside the caption area of a window. There are two more messages that our subclass procedure needs to handle. The messages are WM_NCACTIVATE and WM_SETTEXT. Both of these messages cause a window caption to be repainted, which is going to cause our inserted buttons to be painted over. In the case of WM_NCACTIVATE, the caption is painted in either active or inactive colours whenever the window is activated or deactivated. As for WM_SETTEXT, this message is received whenever the window text is to be changed, causing the caption to repainted.

Unfortunately for us neither of these repaints occur through the WM_NCPAINT message, which is a design flaw in Windows as far as I can make out. The problem still remains though. The solution is to prevent both messages from performing any painting whilst letting them do everything else. This is achieved in four steps:

  1. Turn off the WS_VISIBLE style for the window, which stops any painting from occuring.
  2. Calling the old window procedure to handle the message.
  3. Turn WS_VISIBLE back on again.
  4. Paint the window frame and caption using our custom code.

The code looks like this.

?
LRESULT foobar(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    DWORD ret, dwStyle;
    dwStyle = GetWindowLong(hwnd, GWL_STYLE);
    // turn OFF WS_VISIBLE
    SetWindowLong(hwnd, GWL_STYLE, dwStyle & ~WS_VISIBLE);
    // perform the default action, minus painting
    ret = CallWindowProc(ctp->wpOldProc, hwnd, msg, wParam, lParam);
    // turn ON WS_VISIBLE
    SetWindowLong(hwnd, GWL_STYLE, dwStyle);
    // perform custom painting
    Caption_NCPaint(hwnd, (HRGN)1);
    return ret;
}

This same code can be used to process WM_SETTEXT and WM_NCACTIVATE.

Conclusion

There is another option for placing buttons over the caption bar. You could simply create a button using CreateWindow with the WS_POPUP style. This button can be positioned over the window's title bar, and will look and act just like a normal button. This method will work, but it's not perfect. There will be window focus problems, clipping and painting problems, and the hassle of always making sure that the button is in the right place, and never overlapping any windows it shouldn't be.

The method of inserting titlebar buttons described here is the preferred way to perform this type of customization. Well, I hope you enjoyed this article and found it useful. If you have any feedback, I'd be glad to here it.

1 Oct 2001

Posted in Tutorials

win32
Powered by Drupal


URL Control | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
urlctrl.zip

Published on 22 Jan 2012

10.78 Kb

Download
urlctrl2.zip

Published on 22 Jan 2012

10.62 Kb

URL Control

A simple HTTP URL control

Updated 6th Jan 2005

Ralph Bittmann has kindly sent an update of the URL Control. It has some nice extra features:

  • Keyboard support for tabbing, focus and space key
  • Window resizing to fit the displayed text
  • Better font support
  • Better cursor support

Introduction

This tutorial will teach you how to implement a "URL" control, which pops up a browser when you click on the hyperlink. The control will be based around a simple static text label, and we're going to customize it so that it looks and feels like a hyperlink that you would find in a web-page.

Our aim is to implement a single function, which takes a window handle and a URL, and converts the specified static control into a URL control. This is what the function prototype will look like:

?
BOOL StaticToURLControl(HWND hDlg, UINT uStaticId, TCHAR *szURL, COLORREF crLink);

The function above will actually take a dialog's window handle and the control ID of a static label. Because I envisage this URL control will be most commonly found in dialog boxes this method provides the simplest interface.

Subclassing again

The easiest way to create this URL control is to subclass a static control, and add on the functionality we require. The subclass procedure will need to perform these tasks:

  • Change the mouse cursor to a "hand" when the mouse moves over the control.
  • Change the colour of the label (Blue for normal, Purple for a visited link).
  • Display the static label with an underlined font.
  • Display a browser when the user clicks on the control.

Before we start to implement these features, we need to actually perform the subclass and initialize the static control correctly. The structure below will hold our URL control state.

?
typedef struct
{
    TCHAR szURL[_MAX_PATH];    // desination URL
    WNDPROC  oldproc;           // old window procedure
    COLORREF crLink;            // current link colour
    COLORREF crVisited;         // link colour if visited} URLCtrl;

A hyperlink in a web page has several properties which we need to emulate. In addition to the visible display text, a hyperlink also has a destiniation URL which is visited when the link is clicked on. Now, a standard static control already has a text label which you set with SetWindowText or WM_SETTEXT. However, we need to somehow specify what the destination URL is, so the structure above includes this information.

The colour of a hyperlink is also important. A hyperlink has two colours - blue when unvisited, and purple when the link has been visited. The two COLORREF structure members will represent these properties.

The function below shows how to convert a static control into a URL control. It performs a number of steps, described below.

  1. Allocate memory for the URL state structure.
  2. Set the URL destination text.
  3. Set the hyperlink colours.
  4. Replace the static control's window procedure with our custom one.
  5. Associate the URL state with the new URL control.
?
BOOL StaticToURLControl(HWND hDlg, UINT staticid, TCHAR *szURL, COLORREF crLink)
{
    HWND hwndCtrl = GetDlgItem(hDlg, staticid);
    // Allocate memory for the URL structure
    URLCtrl *url = (URLCtrl *)HeapAlloc(GetProcessHeap(), 0, sizeof(URLCtrl));
    // set the URL text (not the display text)
    if(szURL) lstrcpy(url->szURL, szURL);
    else url->szURL[0] = _T('\0');
    // set the hyperlink colour
    if(crLink != -1) url->crLink = crLink;
    else url->crLink = RGB(0,0,255);
    // set the visited colour
    url->crVisited = RGB(128,0,128);
    // subclass the static control
    url->oldproc = (WNDPROC)SetWindowLong(hwndCtrl, GWL_WNDPROC, (LONG)URLCtrlProc);
    // associate the URL structure with the static control
    SetWindowLong(hwndCtrl, GWL_USERDATA, (LONG)url);
    return TRUE;
}

The actual subclass procedure

A subclass procedure is just a window procedure. The only difference is, instead of calling DefWindowProc you must use CallWindowProc. The skeleton function below performs no customization. All it does at present is to free the URL structure when the window is destroyed.

?
// Subclass window procedure for a static control
LRESULT CALLBACK URLCtrlProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
    // get the URL state structure for this window
    URLCtrl  *url     = (URLCtrl *)GetWindowLong(hwnd, GWL_USERDATA);
    WNDPROC   oldproc = url->oldproc;
    switch(iMsg)
    {
    case WM_NCDESTROY:
        HeapFree(GetProcessHeap(0), url);
        break;
        // handle other messages here.
    }
    // perform default processing for all other messages
    return CallWindowProc(oldproc, hwnd, iMsg, wParam, lParam);
}

The rest of this tutorial will show you what is necessary to fill in the rest of the subclass procedure.

Changing the mouse cursor

This is the easiest task to perform, so I'll do this first to get started. All we need to do is handle the WM_SETCURSOR message in our subclass procedure. We use the SetCursor API call to actually change the mouse cursor, and then return TRUE to tell Windows to stop any further processing.

?
case WM_SETCURSOR:
    SetCursor(hCurs);
    return TRUE;

It's that simple. The only other thing to do is to create an appropriate cursor. The simplest way to do this is to use the LoadCursor API call, and include a cursor as a resource in your main executable. This is slightly disadvantagous because you have to remember to include this cursor every time you want a URL control.

Our solution will be to create the cursor manually, with the CreateCursor API call. Using this method, our URL source file will be completely standalone, with no other dependencies. This is what the function looks like (from MSDN):

?
HCURSOR CreateCursor(
  HINSTANCE hInst,         // handle to application instance
  int xHotSpot,            // x coordinate of hot spot
  int yHotSpot,            // y coordinate of hot spot
  int nWidth,              // cursor width
  int nHeight,             // cursor height
  CONST VOID *pvANDPlane,  // AND mask array
  CONST VOID *pvXORPlane   // XOR mask array
);

We have to build those mask arrays somehow. For a standard 32x32 monochrome cursor, we require two 128 byte arrays, which represent the bit-masks used to create the cursor image. The download includes these two arrays, but it's worth mentioning how you can create your own by extracting the bit masks from a cursor resource.

  1. Create a cursor using the Visual C++ resource editor.
  2. Locate the *.CUR file in your project directory.
  3. Open the cursor file in your favourite Hex Editor. I recommend my very own HexEdit :-)
  4. Select the last 128 bytes in the .CUR file. This is the XOR mask.
  5. The mask is stored upside down for some reason, so you need to reverse each line in the bitmap. We can calculate that 1 line in the cursor requires 4 bytes in the mask, because 128 bytes divided by 32 lines equals 4.
  6. Reverse the selection using the Edit->Reverse menu command ->
  7. Enter "4" in the Item Width edit field. Click on OK.
  8. Export the raw bytes as a C-style array of BYTES.
  9. Select the File->Export menu command.
  10. Select "C Source" and "Byte" export types.
  11. Select a file name and hit Export.
  12. Repeat from step 4, but this time select the second-last 128 byte block before the current selection. This is the AND mask.

You should now have two *.C files saved somewhere. They will look like this:

?
/* Generated by HexEdit */
/* C:\MSVC\My Projects\URLCtrl\cur00001.cur */
BYTE XORMask[128] =
{
  0xff, 0xff, 0xff, 0xff,
  0xf9, 0xff, 0xff, 0xff,
  ..., ...
};

Once you've got your two arrays included in your source, you can create a cursor like this:

?
hCurs = CreateCursor(GetModuleHandle(0), 5, 2, 32, 32, XORMask, ANDMask);

Just store this cursor handle somewhere so that the WM_SETCURSOR handler has access to it. Although it may seem as if I've side-tracked a little from the original task, I think this technique is really useful so I included it anyway!

Painting the URL control

Now for the artistic part. We need to customize both the font and the colour of the static control if we are to create a proper hyperlink. There are two ways to do this. The first is to handle the WM_CTLCOLORSTATIC message in the static control's parent window (the dialog box). However, this means that every time we want to use a URL control we have to manually handle this message. A better solution is to completely take over drawing in the static control's WM_PAINT handler.

Find the text alignment.

A standard static control can have its text aligned to the left, right or center. In addition, text can also be centered vertically. Utimately we will be using DrawText to draw the text, so we need to map the static control text-alignment styles to those that can be specified with DrawText. The code below shows you how.

?
DWORD dwStyle   = GetWindowLong(hwnd, GWL_STYLE);
DWORD dwDTStyle = DT_SINGLELINE;
// Test if centered horizontally or vertically
if(dwStyle & SS_CENTER)      dwDTstyle |= DT_CENTER;
if(dwStyle & SS_RIGHT)       dwDTstyle |= DT_RIGHT;
if(dwStyle & SS_CENTERIMAGE) dwDTstyle |= DT_VCENTER;

Set the text colours before drawing.

?
SetTextColor(hdc, url->crLink);
SetBkColor  (hdc, GetSysColor(COLOR_3DFACE));

Create an underlined font.

This one is easy. We first of all find the default GUI font that we would be drawing with normally. Then, we create a new font based on that one, but with the underline style set, like this:

?
// Get the default GUI font
LOGFONT lf;
HFONT hf = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
// Add UNDERLINE attribute
GetObject(hf, sizeof lf, &lf);
lf.lfUnderline = TRUE;

// Create a new font
hfUnderlined = CreateFontIndirect(&lf);
// Use the font
SelectObject(hdc, hfUnderlined);

Draw the text.

The static control already has its window text set, so we'll just use that instead of having to store our own copy. Note the dwDTStyle flag for DrawText, which we worked out earlier.

?
TCHAR szWinText[200];
GetWindowText(hwnd, szWinText, sizeof szWinText);
DrawText(hdc, szWinText, -1, &rect, dwDTStyle);

Launch the Default Browser

At this point we have a static control that looks like a hyperlink, but it doesn't do anything yet. We only need to handle three mouse messages to launch a browser when the user clicks on the static control.

Enable mouse messages (WM_NCHITTEST)

This is the most important message. A standard static control will return HTTRANSPARENT for this message, which means that all mouse messages get passed to the parent window instead of the static control. We have to override this behaviour so that our subclass procedure receives the mouse messages instead.

?
case WM_NCHITTEST:
    return HTCLIENT;

Mouse-down (WM_LBUTTONDOWN)

All we need to do for this message is to set a flag saying "The user has clicked the mouse down in our window". This is so that when the mouse is released in the same window we can interpret this as a mouse click. We need the flag, because it is quite possible for a window to receive a button-up message even if the mouse button wasn't pressed down in that window.

?
case WM_LBUTTONDOWN:
    fClicking = TRUE;
    break;

Mouse-up (WM_LBUTTONUP)

This is when we launch the browser.

?
case WM_LBUTTONUP:
    if(fClicking)
    {
        fClicking = FALSE;
        // Change the link colour to purple
        url->crLink = url->crVisited;
        InvalidateRect(hwnd, 0, 0);
        UpdateWindow(hwnd);
        // Open a browser
        ShellExecute(NULL, _T("open"), url->szURL, NULL, NULL, SW_SHOWNORMAL);
    }

The default browser is launched by a call to ShellExecute. To get this function to display a browser, all we do is specify "open" as the second parameter, and pass a pointer to a string containing a fully qualified URL as the third parameter. ShellExecute does the rest for us, by displaying a browser window for the specified URL.

Conclusion

That, in a nutshell, is it. The source-code download includes the complete URL control source, and a mini test application to demonstrate it. If you want to use a URL control in one of your applications, just include "URLCtrl.h" in your source, and call the following function:

?
StaticToURLControl(hDlg, IDC_HYPER1, "<a href="http://www.mysite.com"">http://www.mysite.com"</a>, -1);

Just a quick mention on how to use the function above. In the resource editor for your dialog box, you need to give your static control a unique ID (instead of ID_STATIC), because by default all static controls have an ID of -1, which means that you can't locate a standard static control using GetDlgItem.

The second issue is the URL text. You need to specify a complete URL, including the http:// prefix, in order for ShellExecute to interpret the string as a URL and launch the browser accordingly.

The last issue is specifying the colour for an unvisited hyperlink. You can specify either a RGB(r,g,b) value, or just pass -1, which gives the control the default blue colour.

Have fun!

James.

1 Oct 2001

Posted in Tutorials

win32
Powered by Drupal


Custom Controls | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
custctrl.zip

Published on 22 Jan 2012

7.37 Kb

Download

Custom Controls

How to create a custom win32 control

A custom control is any child window which displays information or allows the user to interact with it in some way. This article describes the steps required to create a custom user control from scratch, using pure Win32 techniques. This custom control will not be an ActiveX control, or possess any other magical properties. Rather, we will be creating a simple control, similar to the way edit controls, buttons or listboxes work.

The example control we will build will be a simple text label, but will change colour whenever the user clicks on it. This simple example will be sufficient to help explain all the steps necessary to create any custom control.

Custom Control demo

Getting Started

A custom control is nothing special. It is just a standard window, but created with the WS_CHILD style set. The custom part comes in because we write a new window procedure to provide the necessary control display and interaction.

So, first things first. We're going to create a new source-file to put just the custom-control code in. Call this CustCtrl.c or something. If you take this modular approach from the start you will find it so much easier to develop and maintain your code. This source file just needs to contain the following two lines for now:

?
#include <windows.h>
#include <tchar.h>

The <tchar.h> file enables us to write Unicode-compatible windows programs. In a few places in the code, you will see things like "TCHAR" and "_T". These are macros defined in <tchar.h>, which help us to easily create Unicode applications, if we want. Our custom control won't be Unicode, but it could be if we used the correct compiler settings.

In order to create a new type of window, we need to register a new window class. This is achieved with the RegisterClassEx API call, shown below.

?
TCHAR szClassName[] = _T("CustCtrl345");
void InitCustomControl()
{
    WNDCLASSEX wc;
    wc.cbSize         = sizeof(wc);
    wc.lpszClassName  = szClassName;
    wc.hInstance      = GetModuleHandle(0);
    wc.lpfnWndProc    = CustWndProc;
    wc.hCursor        = LoadCursor (NULL, IDC_ARROW);
    wc.hIcon          = 0;
    wc.lpszMenuName   = 0;
    wc.hbrBackground  = (HBRUSH)GetSysColorBrush(COLOR_BTNFACE);
    wc.style          = 0;
    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = 0;
    wc.hIconSm        = 0;
    RegisterClassEx(&wc);
}

So far so good. The next step is to write a window procedure (CustWndProc) which the custom control uses to process it's messages. This is just a standard window procedure, like the one shown below.

?
LRESULT CALLBACK CustWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
    default:
        break;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

Now, it may look as if this window procedure doesn't actually do anything, but this is not the case. The DefWindowProc API call performs alot of default processing (window focus, painting, activation etc) for a window. It is only when we need to provide additional functionality (like drawing the window focus) that we need to start filling in that switch statement to handle specific windows messages.

Creating the control

Now we can create a custom control! There are two ways to do this. The first is to manually create the control at run-time (probably in the main window's WM_CREATE message, or something similar). This is achieved with the CreateWindowEx API call, like this:

?
HWND CreateCustomControl(HWND hwndParent)
{
    HWND hwndCtrl;
    hwndCtrl = CreateWindowEx(
                 WS_EX_CLIENTEDGE, // give it a standard border
                 szClassName,
                 _T("A custom control"),
                 WS_VISIBLE | WS_CHILD,
                 0, 0, 100, 100,
                 hwndParent,
                 NULL, GetModuleHandle(0), NULL
               );
    return hwndCtrl;
}

The second way, assuming you are using some kind of resource editor (like in Visual C++), is to create a custom control in a dialog template, in the exact same way you might place edit controls or list boxes on a dialog box. The only difference is, you create a "Custom Control", and manually define the name of the window class (in this case, "CustCtrl345"). When your dialog is created, Windows automatically creates the custom control for you, and gives it a control ID.

Creating a custom control with visual studio

Custom control state information

At some point in time, you will want to make the control do something. And, unless your control is going to be extremely simple, you will undoubtably want to define variables, text strings or arrays to represent the state of the control. For example, in a custom list control, you would at the very least need to keep track of the items in the list, using an array or linked list. You would also need a variable which keeps track of how many items there are in your list. In addition, if you want your control to be scrollable, you will need to keep track of the scrollbar position, and the minimum and maximum scroll ranges.

You need to take my word that you will need to define a structure (or class, if you are using C++) which will encapsulate ALL of the control's state information. If you take this approach (and avoid using global variables), you will be able to create multiple custom controls at the same time, and each one will look after itself.

We need to decide what attributes our custom control will have. The list below describes these attributes.

  • Text colour (Foreground and Background)
  • Display text (what the control actually displays)
  • Font (what type-face the text will be drawn in)

The following structure will hold some simple state information.

?
typedef struct
{
    COLORREF crForeGnd;    // Foreground text colour
    COLORREF crBackGnd;    // Background text colour
    HFONT hFont;        // The font
    HWND hwnd;         // The control's window handle
} CustCtrl;

The one element missing from this structure is the display text. For our simple example, this is not necessary, because every window has it's own window text. Therefore we will just use this standard window text when we draw the control. For more complicated controls (lists, edit controls), you would need to store the text yourself.

Associating a structure with the custom control

At this point you will hit a stumbling block. The problem is, your custom window procedure is a simple callback function, which processes all messages for all custom controls you create. Depending on which window is currently having it's message processed, we will have to obtain the correct state structure for that window, and use that structure when processing the message.

There are many ways to "attach" a structure to a window. Which one you use can depend on many things, but I will describe each method below. First though, we will define two simple functions, which will set and retrieve our custom structure for a window:

?
CustCtrl * GetCustCtrl(HWND hwnd)
{
    // get pointer to structure, then return it
    CustCtrl *ccp = ???
    return ccp;
}
void SetCustCtrl(HWND hwnd, CustCtrl *ccp)
{
    // attach pointer to window
}

The GWL_USERDATA area.

Every window in the system has a 32bit integer which can be set to any value. This 4 byte storage area is enough to store a pointer to a structure. We set this integer using SetWindowLong, and retrieve the integer using GetWindowLong. Using this technique, our function will look like this:

?
CustCtrl * GetCustCtrl(HWND hwnd)
{
    return (CustCtrl *)GetWindowLong(hwnd, GWL_USERDATA);
}
void SetCustCtrl(HWND hwnd, CustCtrl *ccp)
{
    SetWindowLong(hwnd, GWL_USERDATA, (LONG)ccp);
}

This method is usually used when subclassing a control rather than writing one from scratch, because there are better alternatives. The problem with this method is that any application or window can set this user-data-area, so you need to be careful it is never used by two conflicting components.

Window Properties

Window properties allow a program to attach multiple 32bit integer values to a window, using a textual string as a way to map each property to the associated integer value. In actual fact, a window property is really a HANDLE value (i.e. a handle to a block of memory, or a GDI resource). However, a HANDLE is still a 32bit integer, so we will store a pointer to a custom structure as a HANDLE.

?
TCHAR szPropName[] = _T("CustCtrlPtr");
CustCtrl * GetCustCtrl(HWND hwnd)
{
    return (CustCtrl *)GetProp(hwnd, szPropName);
}
void SetCustCtrl(HWND hwnd, CustCtrl *ccp)
{
    SetProp(hwnd, szPropName, (HANDLE)ccp);
}

This method will be a little slower than the rest, simply because of the string comparisons that windows will have to do when it retrieves a window property for us. It's not much slower though, and because we only need to do this once for every message we receive, it's not much of an overhead at all.

Extra window bytes

This is the best way to go if you are writing a control from scratch. When you initially register a control's window class, you have the option of specifying how many extra bytes of user-storage each window of that class will contain. If we set this value to be the size of a pointer (to our state structure), then we can use this special-purpose space exclusively for our custom control. This then leaves the GWL_USERDATA area for other purposes.

?
// Register the window class.
wc.cbWndExtra     = sizeof( CustCtrl * );
?
CustCtrl * GetCustCtrl(HWND hwnd)
{
    return (CustCtrl *)GetWindowLong(hwnd, 0);
}
void SetCustCtrl(HWND hwnd, CustCtrl *ccp)
{
    SetWindowLong(hwnd, 0, (LONG)ccp);
}

The extra window bytes are always accessed using a zero-based offset (when using GetWindowLong). Because we reserved space for one 32bit integer, this is accessed from the start of the extra bytes, therefore we use an index of zero.

Indirect lookup tables

The last method (which MFC uses) is to use a separate lookup table, which maps window handles to structures. This is basically just an array or hash table, with entries like this:

?
typedef struct
{
    HWND hwnd;
    CustCtrl *ccp;
} WndLookup;
WndLookup big_lookup[MAX_CUST_WINDOWS];

Whenever we need to retrieve the custom structure from a window, we need to search the array or hash table for the correct entry. This is fairly quick when we only have a couple of custom windows at any time, but when we start to create alot of windows, this method will add quite an overhead. It is also difficult to intergrate this technique into a multi-threaded application.

Assembly language thunks

This method is included for completeness, and is quite different to the others described above. The technique is used by the ATL and WTL C++ template libraries, and is a very quick method of retrieving an integer associated with a window. It's pretty complicated though, so our basic method is still preferred.

The basic idea is to replace the window procedure for a custom control with a small assembly language stub which modifies the first argument to the window procedure (the handle to the window), and replaces it with a pointer to the CustCtrl structure. The stub can look something like this:

?
mov  [esp+4], CustCtrl *
jmp  orig_proc

You must understand the environment that this stub executes in, in order to understand how this trick works. When the window procedure needs to be called, the win32 sub-system executes a CALL instruction to pass control to the window procedure in question (which is not the window procedure at all, but the assembly-language stub. However, the very act of making a function call to a window procedure (real or not) results in the stack being set to the following state:

?
...     ...
esp+10  [lParam]
esp+0C  [wParam]
esp+08  [Message]
esp+04  [HWND]      ->    [CustCtrl *]
esp+00  [address to return to when function returns]

The stub must perform two key actions when executed:

  1. Replace the HWND parameter on the stack with a pointer to the class or structure that we want to associate with the window.
  2. Pass control to the real window procedure, now with it's first argument modified.

The stub is generated at run-time, for each window and class/structure instance that is required. Note that the stub is different each time, because the structure and window procedure will be different. The actual op-codes for the MOV and JMP instructions must be generated for each specific case.

As you can imagine, this technique is very fast. The drawback though is that the window procedure can't make any reference to the HWND parameter - it has changed to be a pointer to the class / structure instead. This means that this structure MUST include the original HWND as a member, otherwise operations on the window would become impossible.

The window procedure, which can be a class-member-function, will look like this:

?
UINT WINAPI CustCtrl::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam);

Note that there is always a hidden first parameter to every C++ member function - the *this pointer. This means that the class-window procedure has full access to the whole class instance.

To understand more on this topic, read the following article by Fritz Onion:

Thunking WndProcs in ATL

Creating the control, Take 2

Now that we know how to attach a custom structure to a window, we can use this technique when we create a custom control. The actual CreateWindowEx call can remain the same, but our window procedure needs to change to incorporate the new CustCtrl structure. The differences are this:

  • Allocate a new structure when the window is first created.
  • Initialize the structure contents to default values.
  • Attach the structure to the window (using a pointer to the structure).
  • Free the structure memory when the window is destroyed.

The custom control's window procedure will now need to look this this:

?
LRESULT CALLBACK CustWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    // retrieve the custom structure POINTER for THIS window
    CustCtrl *ccp = GetCustCtrl(hwnd);
    switch(msg)
    {
    case WM_NCCREATE:
        // Allocate a new CustCtrl structure for this window.
        ccp = malloc( sizeof(CustCtrl) );
        // Failed to allocate, stop window creation.
        if(ccp == NULL)
            return FALSE;
        // Initialize the CustCtrl structure.
        ccp->hwnd      = hwnd;
        ccp->crForeGnd = GetSysColor(COLOR_WINDOWTEXT);
        ccp->crBackGnd = GetSysColor(COLOR_WINDOW);
        ccp->hFont     = GetStockObject(DEFAULT_GUI_FONT);
        // Assign the window text specified in the call to CreateWindow.
        SetWindowText(hwnd, ((CREATESTRUCT *)lParam)->lpszName);
        // Attach custom structure to this window.
        SetCustCtrl(hwnd, ccp);
        // Continue with window creation.
        return TRUE;
    // Clean up when the window is destroyed.
    case WM_NCDESTROY:
        free(ccp);
        break;
    default:
        break;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

Note the use of the WM_NCCREATE and WM_NCDESTROY messages here. These are the first and last messages to be received by a window, respectively. By using these messages to allocate and free our custom structure, we can be sure that this structure will be in existance for all other window messages we receive.

Adding control functionality

At this point we can create as many custom controls as we desire. The windows won't actually display or do anything, but you can manipulate them, size them etc, just like any other window you create. Let's start to add the control's functionality though.

The first step is to start writing message handler functions for every message you want to handle. It is a very good idea to write a separate function for each message. This keeps the window procedure neat and simple, and keeps your code nice and modular. I can't stress this point enough - get into the habit of writing separate functions right from the start, because it makes writing your code so much simpler in the long run.

Painting the control

Whenever windows wants us to update the contents of our window (the client area), a WM_PAINT message will be sent. So, whenever the WM_PAINT message is received, we need to call our control's painting routine.

?
case WM_PAINT:
    return CustCtrl_OnPaint(ccp, wParam, lParam);

Note how the pointer to the control structure is passed to the message handler function. This will be the same for any message we handle. Now, the actual paint handler will look something like this:

?
LRESULT CustCtrl_OnPaint(CustCtrl *ccp, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT  ps;
    HANDLE hOldFont;
    TCHAR szText[200];
    RECT         rect;
    // Get a device context for this window
    hdc = BeginPaint(ccp->hwnd, &ps);
    // Set the font we are going to use
    hOldFont = SelectObject(hdc, ccp->hFont);
    // Set the text colours
    SetTextColor(hdc, ccp->crForeGnd);
    SetBkColor  (hdc, ccp->crBackGnd);
    // Find the text to draw
    GetWindowText(ccp->hwnd, szText, sizeof(szText));
    // Work out where to draw
    GetClientRect(ccp->hwnd, &rect);
    // Find out how big the text will be
    GetTextExtentPoint32(hdc, szText, lstrlen(szText), &sz);
    // Center the text
    x = (rect.right  - sz.cx) / 2;
    y = (rect.bottom - sz.cy) / 2;
    // Draw the text
    ExtTextOut(hdc, x, y, ETO_OPAQUE, &rect, szText, lstrlen(szText), 0);
    // Restore the old font when we have finished
    SelectObject(hdc, hOldFont);
    // Release the device context
    EndPaint(ccp->hwnd, &ps);
    return 0;
}

Undocumented painting tips

I just want to mention an important feature of Windows here. The standard documention states that wParam and lParam will both be zero for the WM_PAINT message. This is fine, because we can do everything we want with the BeginPaint / EndPaint technique.

However, for alot of standard controls, Windows will sometimes send a WM_PAINT message with wParam set to a handle to a device context. In other words, Windows sometimes supplies a device-context for you, which will result in faster drawing. This means that it is not strictly necessary to use the BeginPaint/EndPaint pair all of the time. To take advantage of this scenario, you could check wParam to see if it is zero or not. If it isn't, then instead of using BeginPaint to get a device context, just use wParam as your HDC. i.e.

?
if(wParam == 0)
    hdc = BeginPaint(ccp->hwnd, &ps);
else
    hdc = (HDC)wParam;

Don't forget to do the same test when you come to call EndPaint - in fact, don't do anything when you have a pre-supplied HDC from Windows.

Now, you need to be careful using this technique, because the device context that windows supplies will not be initialized to it's default state, so you need to make sure that you set the device context up in the correct mapping mode, set the correct colours etc. (i.e, don't assume that the device context will be in a certain state). Also, you must restore ANY setting that you modify, be it font, mapping modes, colours etc.

The custom control presented here does not use this alternative painting method, so you can safely ignore this information if you want.

Preventing flicker

Currently, the control will flicker slightly each time it is painted. This is because it is getting painted twice for every WM_PAINT it receives. The problem is the WM_ERASEBKGND message, which is sent every time we call BeginPaint. This isn't a problem really - Windows is doing us a favour, because the default action for WM_ERASEBKGND is to draw a nice window background for us (using the window's default background brush), which we can then paint on top of in the WM_PAINT handler.

However, our WM_PAINT handler also draws the control's background, so there is no point in this happening twice. Therefore, we need to prevent the default WM_ERASEBKGND behaviour from happending. As usual, there are a number of ways to do this.

  • Set the window's background brush to NULL. (Set the hbrBackground member of the WNDCLASS structure to zero when you register the window class).
  • Return non-zero in the WM_ERASEBKGND message handler.

Any one of these will steps will prevent the WM_ERASEBKGND message from clearing the window. We will therefore choose the last option, because it is the simplest to implement:

?
case WM_ERASEBKGND:
    return 1;

Responding to user interaction

Our custom control will change colour whenever the user clicks the mouse on it. Therefore the next message handler will be for the WM_LBUTTONDOWN message.

?
case WM_LBUTTONDOWN:
    return CustCtrl_OnLButtonDown(ccp, wParam, lParam);
?
LRESULT CustCtrl_OnLButtonDown(CustCtrl *ccp, WPARAM wParam, LPARAM lParam)
{
    COLORREF col = RGB( rand()%256, rand()%256, rand()%256 );
    // Change the foreground colour
    ccp->crForeGnd = col;
    // Use the inverse of the foreground colour
    ccp->crBackGnd = ((~col) & 0x00ffffff);
    // Now redraw the control
    InvalidateRect(ccp->hwnd, NULL, FALSE);
    UpdateWindow(ccp->hwnd);
    return 0;
}

Receiving input focus

When a user clicks on a window or control it does not automatically receive the keyboard-input focus. Think about it for a while: controls like toolbars and static controls never cause the input-focus to change when you click on them so there must be something else we need to do when the mouse is clicked in our window.

Rather than using the WM_LBUTTONDOWN message (which can be received many times by a window) we will uFor our control to receive input focus we must react to the WM_MOUSEACTIVATE message:

?
case WM_MOUSEACTIVATE:
    SetFocus(hwnd);
    return MA_ACTIVATE;

By manually setting the input-focus to our control we

Font support

Currently, our custom control only supports the default system font, so we will handle the WM_SETFONT message. Handling this message will allow us to change the font whenever requested by windows.

?
case WM_SETFONT:
    return CustCtrl_OnSetFont(ccp, wParam, lParam);
?
LRESULT CustCtrl_OnSetFont(CustCtrl *ccp, WPARAM wParam, LPARAM lParam)
{
    // Change the font
    ccp->hFont = (HFONT)wParam;
    return 0;
}

Mission Completed

At this point in time we need go no further. What you have is a complete custom control. It is now up to you to experiment with the control, to add further features, or to start again using the steps described to create your very own control.

There are still a few things I want to say about writing custom controls, so read on if you havn't fallen asleep yet!

Using message cracker macros

Message Crackers are pre-processor macros, define in WINDOWSX.H (available with the Platform SDK).

If you are using C++

It is quite possible to use C++ to write your custom control. In fact, it is alot easier than using plain C, because you can create a miniture object framework which makes writing the window procedure a dream. Instead of using a structure to define the control state, you can use a class. And the message handler functions can become member functions of that class.

As a quick example, let's redefine the structure we used for the custom control:

?
class CustCtrl
{
public:
    CustCtrl(HWND h);
    ~CustCtrl();
    // message handlers
    LRESULT OnPaint       (WPARAM wParam, LPARAM lParam);
    LRESULT OnLButtonDown (WPARAM wParam, LPARAM lParam);
    LRESULT OnSetFont     (WPARAM wParam, LPARAM lParam);
    // window procedure
    static LRESULT CALLBACK WndProc(
       HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam
    );
private:
    COLORREF crForeGnd;    // Foreground text colour
    COLORREF crBackGnd;    // Background text colour
    HFONT hFont;        // The font
    HWND hwnd;         // The control's window handle
};

The window procedure now looks like this:

?
LRESULT CALLBACK CustCtrl::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    // retrieve the custom structure POINTER for THIS window
    CustCtrl *ccp = GetCustCtrl(hwnd);
    switch(msg)
    {
    // Allocate a new CustCtrl class for this window.
    case WM_NCCREATE:
        ccp = new CustCtrl(hwnd);
        SetCustCtrl(ccp);
        return (ccp != NULL);
    // Clean up when the window is destroyed.
    case WM_NCDESTROY:
        delete ccp;
        break;
    // Handle messages
    case WM_PAINT:         return ccp->OnPaint(wParam, lParam);
    case WM_ERASEBKGND:    return 1;
    case WM_LBUTTONDOWN:   return ccp->OnLButtonDown(wParam, lParam);
    case WM_SETFONT:       return ccp->OnSetFont(wParam, lParam);
    default:
        break;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

Notice the difference in the way the C++ class is allocated. The class's constructor can be used to initialize the object. Also, look at the way the message handler functions are called through a pointer to the class. There is no need to pass the pointer as the first parameter, because a C++ member function has access to the object's class members. This means, in our message handler functions, there is no need to constantly write ptr->attribute, because C++ does this automatically for you with the implicit this pointer. Look at this small example:

?
LRESULT CustCtrl::OnSetFont(WPARAM wParam, LPARAM lParam)
{
    // Change the font
    hFont = (HFONT)wParam;
    return 0;
}

This is the advantage of using C++. It makes writing this type of object-oriented program alot easier. Now, just one last thing, which is very important. In the C++ class, I defined the window procedure to be a static function. I will explain why this important detail is necessary.

A standard C++ member function always has an implicit first argument, the *this pointer. Whenever you call a member function, the C++ compiler automatically includes this hidden argument. This means that our window procedure, if it had been a normal class member, would really have looked like this:

?
LRESULT CALLBACK CustCtrl::WndProc( CustCtrl *this, HWND hwnd, ...);

You never declare a member function like this, but this is how it works "behind the scenes". Now, you are probably aware that this function prototype is incompatible with the standard window procedure prototype. In fact, it is impossible to use a member function as a window procedure. Therefore, we need to define the window procedure as a static member, so that the *this pointer is omitted from the function prototype.

?
LRESULT CALLBACK CustCtrl::WndProc(HWND hwnd, ...);

Obviously this means that the window procedure cannot access any member variables. This is why we have to obtain a pointer to the correct class for each window, and call the member functions through that pointer. This is a small inconvenience though for the design benefits that C++ gives us.

Important: Although a custom control can be written using C++, this is as far as it goes. The "interface" to the control (i.e. how it is created, how to move it / size it) hasn't changed at all. This makes it possible to use C++ to create a custom control, and then use it in a C project. This way you get the best of both worlds.

Conclusion

We have covered alot of ground in this tutorial, but it was necessary because custom controls, whilst not difficult, require careful coding and a reasonable understanding of Windows programming.

Although the example control presented in this tutorial was very simple, the concepts are exactly the same as for a more complicated control. I have shown you the techniques required to build a custom control from scratch. It is now up to you to take these techniques and apply them to your own projects.

Well, happy coding,

James.

20 Nov 2001

Posted in Tutorials

win32
Powered by Drupal


Flicker-free Drawing | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Flicker-free Drawing

Techniques to eliminate flicker from your applications

Flicker is the sign of sloppy programming and a lack of attention to detail. There is no reason why any part of a Windows program should flicker. The aim of this article is to present the reader (that's you) with the techniques used to prevent their windows applications from flickering.

What is flickering?

Flicker is simply this: the display of one image over the top of another in rapid succession. The result of this is screen flicker, where you can see one image briefly before another one is shown on top. Personally I find applications that "flicker" annoying to use, for this one reason: If the user-interface has been badly coded, then what does this say about the rest of the application, the part that you trust your data with? An application that has a smooth, fast user interface inspires confidence in it's users - it's as simple as that.

An application can flicker in many ways. The most common cause is when a window is resized, causing the contents to flicker badly as it is redrawn.

Only draw things once

This is the golden rule when doing any kind of painting on a computer, be it Windows or whatever OS you are using. You must never draw over the same pixel twice. A lazy programmer will often avoid putting any thought into the painting process, instead opting to take the easy route.

With the case of flickering, it is your responsiblity to ensure that no "overdraw" occurs. Now, Windows and your computer are fundamentally stupid; they won't do anything unless you instruct them explicitly. If any flickering is occuring, it is because some part of your program has deliberately overdrawn some area of the screen.

This may be because of some explicit command, or something which you have neglected to do. In either case, if your Windows program has a flickering problem, you need to understand how best to remove the problem.

WM_ERASEBKGND

The prime suspect is usually the WM_ERASEBKGND message. This message is sent to a window when it's background needs to be erased. This happens because windows are usually painted using a 2-stage process:

  • WM_ERASEBKGND: Clear the background
  • WM_PAINT: Draw the contents on top

This makes it easy to draw a window's contents: Every time you receive a WM_PAINT message, you know that you have a nice fresh canvas to draw on. However, drawing a window twice (once with WM_ERASEBKGND, once again with WM_PAINT) will cause the window to badly flicker. Just take a look at the standard Edit control in Windows - open up Notepad.exe and resize the window, and see how the contents flicker as it is redrawn.

Right then, how do we avoid erasing the background of a window? There are two methods.

  • Set the window's background brush to NULL. (Set the hbrBackground member of the WNDCLASS structure to zero when you register the window class).
  • Return non-zero in the WM_ERASEBKGND message handler.

Any one of these will steps will prevent the WM_ERASEBKGND message from clearing the window. The last option is usually easiest to implement:

?
case WM_ERASEBKGND:
    return 1;

It is also possible to prevent WM_ERASEBKGND when you invalidate and update a window. The InvalidateRect API call's last parameter specifies whether or not a portion of a window is to have it's background erased when it is next redrawn. Specifying FALSE for this paramter prevents WM_ERASEBKGND from being sent when the window is redrawn.

?
InvalidateRect(hwnd, &rect, FALSE);

Don't draw things when you don't have to

It is quite common for a Windows application to redraw it's entire window contents, even if only a small part of it changed. This is most usually the case when a window is resized - some (but not all) programs redraw the whole window. This is normally not necessary, because when a window is resized, more often than not the previous window contents is left unchanged, and the resize has just uncovered a small border which needs painting. It is not necessary to redraw the entire contents in this case. If a little thought and care is used, the painting algorithms can be written so that only the bare minimum is painted at any one time.

Every window in the system keeps an update region. This region describes the area of a window that has become invalidated and needs repainting. If a windows only updates the required area, and no more, then the window will draw much quicker as a result.

There are several ways to retrieve the update region for a window. The GetUpdateRgn API call retrieves the exact region, be it rectangular, or a more irregular shape. The GetUpdateRect API call retrieves the smallest bounding rectangle that encloses the update region. It is usually easier to just work with a rectangular area like this. The third method is to use the PAINTSTRUCT structure in conjunction with the BeginPaint/EndPaint API calls.

A normal painting procedure looks like this:

?
PAINTSTRUCT  ps;
HDC hdc;
case WM_PAINT:
    hdc = BeginPaint(hwnd, &ps);
    // do painting
    EndPaint(hwnd, &ps);
    return 0;

BeginPaint initializes the ps (PAINTSTRUCT) structure. One member, rcPaint, is a RECT structure which describes the smallest bounding rectangle that encloses the update region (Just like the GetWindowRect API call). By only limiting drawing to just this rectangular region, painting can be dramatically sped up.

Now, Windows automatically clips any drawing you perform outside the update region when you use BeginPaint/EndPaint. This means that there is no way you can draw outside the update region even if you tried. You might think that it is pointless to make sure your code doesn't try to draw outside the update region, even when nothing will be drawn anyway. However, you are still avoiding unnecessary API calls and calculations, so I think it is always worth putting in a little more effort to get things working as fast as possible.

When you just can't help it

There are occasions when you spend alot of time and effort getting your super-duper drawing code working, only to find that your window is still getting redrawn in it's entirety. This is usually the cause of two window class styles - CS_VREDRAW and CS_HREDRAW. When a window class has either of these two styles set, the window contents will be completely redrawn every time it is resized either vertically or horizontally (or both). So, you need to turn off these two class styles. The only way to do this is to make sure your window isn't created with them in the first place, and to prevent this from happening, you have to make sure that CS_HREDRAW and CS_VREDRAW aren't included when the window class is registered.

?
WNDCLASSEX wc;
wc.cbSize  = sizeof(wc);
wc.style   = 0; /* CS_VREDRAW | CS_HREDRAW; */
...
RegisterClassEx(&wc);

The above example is just to help illustrate the point that these two styles must not be included when the window class is registered.

Just a word of warning here: If the main window in an application has these two class styles set, then this will cause all child windows to be redrawn during a resize, even if those children don't have the redraw flags set. This can be avoided by following the next step:

Clipping child windows

Sometimes flickering occurs because a parent window doesn't clip it's children when it paints itself. This results in the entire parent window contents being shown, and the the child windows being displayed on top (causing flicker). This can be easily solved by setting the WS_CLIPCHILDREN style on the parent window.

When a window has this style set, any areas that its child windows occupy are excluded from the update region. So, even if you try to draw over a child control, the clipping region that BeginPaint assigns will prevent you from doing so.

Double-buffing and memory-DC's

A common method to completely eliminate flickering windows is to use a technique called double-buffering. This basic idea is to draw a window's contents into an off-screen buffer, and then transfer this buffer to the screen in one fell-swoop (using BitBlt). This is a pretty good way to reduce flicker, but is often overused, especially by programmers who don't really understand how get efficient drawing working.

The basic way double-buffering works is like this:

?
HDC hdcMem;
HBITMAP hbmMem;
HANDLE hOld;
PAINTSTRUCT  ps;
HDC hdc;
....
case WM_PAINT:
    // Get DC for window
    hdc = BeginPaint(hwnd, &ps);
    // Create an off-screen DC for double-buffering
    hdcMem = CreateCompatibleDC(hdc);
    hbmMem = CreateCompatibleBitmap(hdc, win_width, win_height);
    hOld   = SelectObject(hdcMem, hbmMem);
    // Draw into hdcMem here
    // Transfer the off-screen DC to the screen
    BitBlt(hdc, 0, 0, win_width, win_height, hdcMem, 0, 0, SRCCOPY);
    // Free-up the off-screen DC
    SelectObject(hdcMem, hOld);
    DeleteObject(hbmMem);
    DeleteDC    (hdcMem);
    EndPaint(hwnd, &ps);
    return 0;

This method is a little slow, because the offscreen memory-DC is created from scratch every time the window needs to be drawn. A more efficient method would be to create the memory DC only once, big enough so that the entire window can be painted at any time. When the application terminates, the memory DC would then be destroyed. Both these methods are potentially quite memory-intensive, especially if the memory DC needs to be the size of a screen (1024 * 768 * 32 bytes=2.5 Mb).

Double-buffering will also be twice as slow as it needs to be. Because you are drawing once to the memory-DC, then again during the "blit", you are using up clock cycles when you don't need to. Granted, a fast graphics card will perform a BitBlt very quickly, but it's still wasted CPU.

If your application needs to display quite complicated information (say, like a web-page), then you would need to use the memory-DC method. Take Internet Explorer, for instance. There is no way it would be able to render a web-page with no flickering without using double-buffering.

Double-buffering doesn't have to be used to paint a whole window. Imagine that you had just a small portion of a window that contained a complex graphic object (maybe a semi-transparent bitmap or something). You could use an off-screen DC to draw just this one region, and BitBlt that to the screen, whilst drawing the rest of the window normally.

Sometimes though, with a little careful thinking, it is often possible to avoid double-buffering and draw straight to the screen. As long as you don't break the golden rule, "Never draw over the same pixel twice", you will achieve flicker-free drawing.

Avoiding deliberate overdraw

What I mean by this is the following type of situation. Say, you are custom-drawing the titlebar of a window. You draw the caption first, then draw some additional graphics over the top. Now, whenever the caption needs to be painted, it will flicker. This is because you haven't followed the "golden rule". In this case, the caption is being shown briefly before additional graphics are painted on top, which appear to flicker.

There are two techniques you can use to prevent this type of flickering. The first is to use clipping, the second is to use your brain.

In the case of clipping, you can use the ExcludeClipRect API call to mask out certain areas of a device context. When an area is masked, it is not affected when painted over. Once a background has been drawn, the clipping area can be removed with SelectClipRgn, and another graphic can be painted in the previously masked-out area. By using appropriate masking (or clipping), overdraw can be eliminated in alot of cases.

The other option is to take a more intelligent approach. Imagine you had to draw a grid. A grid would normally be painted by first drawing a blank background, and then drawing a series of lines (horizontal and vertical) to create the grid effect. The problem with this type of approach is that the grid lines will appear to flicker, because the background is briefly appearing underneath each line before the lines are drawn. However, the same effect can be achieved with a different approach. Instead of drawing a single blank background, draw a series of blank squares, separated by a pixel-wide space on each side. When you come to draw the grid lines, they can be placed in the pixel-wide gaps which haven't been painted over yet. The result is the same, but this time there is no flickering because no pixel has been painted over twice.

Using your brain to think around a problem may take slightly longer than the direct "no-brainer" approach, but I think it is worth the extra effort, because the results can be so much better.

Conclusion

Hopefully you should never have to ask the question "Why does my window flicker?" ever again. I have presented the major causes of flickering in a windows program, and also the techniques you can use to remove this flickering. If you encounter flickering in a program you are developing, you should be able to identify the possible causes, and use the techniques described in this tutorial to completely eliminate flicker from your applications.

20 Nov 2001

Posted in Tutorials

win32
Powered by Drupal


Custom Scrollbars | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
coolscroll_src.zip

Published on 22 Jan 2012

78.08 Kb

Download

Custom Scrollbars

Introducing the Cool Scrollbar library, for customizing the appearance of scrollbars

New features in Version 1.2

  • TreeView problem fixed, thanks go to Diego Tártara.
  • A few other minor problems also fixed.

New features in Version 1.1

  • Supports ALL types of window (only in Windows NT, 2000 and XP).
  • Simplified demo application.
  • MFC demo application also included.
  • Added support for Right-left reading windows .
  • Changed calling convention of APIs to WINAPI (__stdcall).
  • Completely standalone (no need for c-runtime).

Introduction

Cool Scrollbars is a library I have written to customize the standard scrollbars of a window. Please note that this library only supports standard window-scrollbars. Separate scrollbar controls are NOT supported. If you want to customize a scrollbar control, then you must devise your own method.

Rather than explain how this library works, I will describe how to use the library in your applications. Cool scrollbars behave exactly like normal scrollbars in a window. However, unlike standard scrollbars, cool scrollbars can be heavily customized. The library is written entirely in the C programming language, and can be compiled to a tiny 14kb! With all features enabled, this rises to around 20kb. This is a pretty small overhead for something which is so complex. Although this library does not require any external libraries such as MFC, ATL, WTL etc, there is NO reason why you cannot use this library in those types of project.

Features

  • Change the size of a window's scrollbars.
  • Insert buttons into the scrollbar area.
  • Use standard bitmaps or metafiles on inserted buttons.
  • Enable tooltips for all inserted buttons.
  • Enable flat-style and hot-tracked scrollbars.
  • Custom-draw, so you can paint the scrollbars with your own bitmaps.

Starting with version 1.1 of the Cool Scrollbar library, is a new feature which makes it possible to add cool scrollbars to any window in your program, even if you don't have access to the source-code. This includes standard Windows Controls (edit, list-boxes) and the Common Controls (ListView, TreeView etc).

Unfortunately this feature is only available when a program is running under Windows NT, 2000 and XP. This is because of the different ways the Windows 9x and NT operating systems map DLLs into a process's address space. This difference prevents a process from hooking API calls when running under Windows 9x, and is the reason why this feature is only available under NT.

Perhaps a later version of the Cool Scrollbar library will address this problem. Until then however, a program which must run under Windows 95,98 or ME is restricted to having cool-scrollbars only on windows for which the source-code is available.

Enable cool scrollbars for ANY window (New in version 1.1)

The Cool Scrollbar download now contains an additional library which provides a program with the ability to enable cool-scrollbars for ANY window in that program. This library (coolsb_detours) uses a technique called binary-rewriting to hook and intercept all of the standard scrollbar API calls inside USER32.DLL. The coolsb_detours library actually relies on the superb Detours package available from research.microsoft.com.

In order to use coolsb_detours, you must goto Microsoft's research site and download the Detours package. There is only one file you actually need, but the Detours license agreement prevents me from distributing this file myself. You can goto the Detours page here:

http://research.microsoft.com/sn/detours

The only file you need from this package is "lib\detours.lib". Simply copy this file into the coolsb_detours directory before you try to compile it. Please read Microsoft's licence agreement for this package before you start - it contains some important information.

Once you have downloaded the Detours package you must add the coolsb_detours project to your workspace, as well as the standard coolsb project. You do not have to include coolsb_detours if you don't need the new feature it offers.

There are two new cool-scrollbar API calls which enable and disable support for all types of window.

BOOL CoolSB_InitializeApp(void);

CoolSB_InitializeApp allows your program to apply cool-scrollbars to any window created by your program. Note that CoolSB_InitializeApp does not enable the cool-scrollbars themselves - this is still achieved with the InitializeCoolSB API call (see below). You would typically call CoolSB_InitApp at the start of your program.

BOOL CoolSB_UninitializeApp(void);

CoolSB_UninitializeApp must be called before your program exits (assuming that you also called CoolSB_InitializeApp)

These two functions will only return successfully when a program is running under Windows NT, 2000 or XP.

Adding cool scrollbars to a window

To add cool scrollbars to a window, call InitializeCoolSB, passing the handle to the window. Unless you are using the coolsb_detours package under Windows NT/2000/XP, you must replace ALL standard scrollbar functions, such as SetScrollInfo or GetScrollInfo, with the equivalent CoolSB_xxx version. There is a cool scrollbar function call for every standard scrollbar API call. These functions behave in the exact same way as the standard API calls. If cool scrollbars haven't been enabled for a window, the cool scrollbar functions will default to the standard API calls automatically. This allows you to turn cool scroll bars on and off without having to write conditional code.

In addition to the standard scrollbar functions, cool scrollbars offer a number of extra features, and also a set of API calls to manipulate these features.

Note: You do NOT need to use the CoolSB_xxx scrollbar functions if you are using the coolsb_detours library.

Changing the size of the scrollbars

CoolSB_SetSize allows to alter the size of either the horizontal scrollbar, or the vertical scrollbar, or both. You should be very sure that you really want to change the size of a scrollbar, because it could annoy a user who has set their system scrollbar sizes to the exact size that they want.

BOOL CoolSB_SetSize(HWND hwnd, int wBar, int nLength, int nWidth);

By using this API call, you can alter the two dimensions of a scrollbar.

  • wBar can be one of the following values: SB_HORZ, SB_VERT or SB_BOTH.
  • nLength refers to either the width of a horizontal scrollbar arrow, or the height of a vertical scrollbar arrow. I chose the term "length" because you can take the length to mean the length of the actual arrow, in the direction it points.
  • nWidth refers to the height of a horizontal scrollbar arrow, or the width of a vertical scrollbar arrow. This parameter is probably the one most people think of when they think of scrollbar "size", because it alters not only the width of the arrow, but the scrollbar margin and the thumb width as well.

By specifying a non-negative integer number, you can specify the size, in pixels, of a scrollbar arrow. The scrollbar size will remain unaffected by the system scrollbar settings, even if the user changes these settings whilst your program is running.

Important. By specifying a negative number, you can set the scrollbar dimensions to a multiple of the system scrollbar sizes. A value of -1 or SYSTEM_METRIC results in scrollbars which are the exact same size as a normal scrollbar. A value of -2 results in scrollbars twice the size of a standard scrollbar, and so-on. By using negative values, the cool scrollbars will always be sized according to the system scrollbar metrics. You do not have to call this function every time the scrollbar system metrics are changed by the user.

You should try to assign the same number to both the length and the width. If you use different values, then your scrollbars will looked squashed in one dimension.

Making Flat Scrollbars

The Cool Scrollbar library supports Flat scrollbars, like the variety found in the common controls library. Unlike the Microsoft flat scrollbars, the cool scrollbars only support two types of flat scrollbars. These are standard flat-looking scrollbars, without "hot-tracking", and normal flat scrollbars, with "hot-tracking" enabled. Hot-tracking is the feature that highlights a portion of a scrollbar when the mouse moves over it.

The CoolSB_SetFlatMode API allows you to give a cool scrollbar enabled window flat scrollbars.

BOOL CoolSB_SetFlatMode(HWND hwnd, int wBar, UINT nFlatMode);

You must specify which scrollbar of a window will be displayed as a flat scrollbar, by using the wBar parameter. This can be either SB_HORZ, SB_VERT, or SB_BOTH if you want both scrollbars to become flat looking. The nFlatMode parameter can be one of three values.

CSBS_FLAT

Normal looking flat scrollbars

CSBS_HOTTRACKED

Hot-tracked flat scrollbars

CSBS_NORMAL

Remove the flat look

Inserting a button into a cool scroll bar

There are four functions available to insert a button into a scrollbar. You can insert buttons to the left or right of a horizontal scrollbar, and above or below a vertical scrollbar.

All of these functions use the SCROLLBUT structure to specify the attributes of each button. You must use the fMask member to specify which members of the SCROLLBUT structure contain valid information.

BOOL CoolSB_InsertButton(HWND hwnd, int wSBflags, UINT nPos,  SCROLLBUT *psb);
  • wSBflags must be either SB_HORZ or SB_VERT
  • nPos is an integer value specifying the position of the inserted button. This can be zero to insert the button in front of all other buttons, or -1 to insert the button after any others.
  • psb is the address of a SCROLLBUT structure which specifies the button properties.

The remaining button functions all deal with buttons that are already inserted into a scrollbar.

BOOL CoolSB_ModifyButton(HWND hwnd, int wSBflags, UINT uItem, BOOL fByCmd, SCROLLBUT *psb);
BOOL CoolSB_RemoveButton(HWND hwnd, int wSBflags, UINT uItem, BOOL fByCmd);
BOOL CoolSB_GetButton   (HWND hwnd, int wSBflags, UINT uItem, BOOL fByCmd, SCROLLBUT *psb);
  • wSBflags must be either SB_HORZ or SB_VERT
  • If fByCmd is TRUE, then uItem is the command identifier of the button, which is specified by the uCmdId member of the SCROLLBUT structure when a button is inserted.
    If fByCmd is FALSE, then uItem is a non-negative integer specifying the position of the button to modify / remove.
  • psb is the address of a SCROLLBUT structure.

Inserted buttons will receive mouse click notifications The NM_CLICK message will be sent for mouse-down events, and the normal WM_COMMAND message for mouse-up events. The NM_CLICK message (sent in the form of a WM_NOTIFY) uses the NMCOOLBUTMSG structure, which contains useful information such as the coordinates of the button.

Tooltips for inserted buttons

When COOLSB_TOOLTIPS is defined in the userdefs.h file, tooltip notifications will be sent to the window if the mouse hovers over one of the inserted buttons. By ignoring these messages, tooltips will not be displayed. However, by handling the WM_NOTIFY message correctly, it is simple to add tooltips to any of the inserted buttons.

You only need to respond to the standard TTN_GETDISPINFO notification in order to display a tooltip. The button's command identifier will be specified in the hdr.idFrom member of the NMTTDISPINFO structure. Below is the sample code required to support tooltips.

case WM_NOTIFY:
{
  NMTTDISPINFO *ttdi;
  ttdi = (NMTTDISPINFO *)lParam;

  if(ttdi->hdr.code == TTN_GETDISPINFO)
  {
      wsprintf(ttdi->lpszText, "This is button %d", ttdi->hdr.idFrom);
      ttdi->hinst = hInst;
  }
  return 0;
}

Custom draw scroll bars

The cool scrollbar library supports a subset of Custom Draw, the feature found with many of the common controls shipped with Windows. Custom Draw allows an application to completely take over the drawing of a window's scrollbars, and replace the scrollbar graphics with a completely user defined look.

Just like the standard Custom Draw facility, drawing requests are sent to a cool scrollbar enabled window via a WM_NOTIFY message.

A NMCSBCUSTOMDRAW structure is used to notify you of drawing operations.

Not all of the Custom Draw functionality is implemented for cool scrollbars. Only three notifications will be sent to a window - CDDS_PREPAINT (to check if you want to custom draw or not), CDDS_POSTPAINT (after drawing has finished) and CDDS_ITEMPREPAINT, for each scrollbar item. A pre-paint and post-paint notification is sent one for each scrollbar, whenever it needs to be painted. These two notifications are not sent when the scrollbar gripper (the dead area) needs to be painted; only an item pre-paint is sent in this case.

It is important to note that the return value from the CDDS_ITEMPREPAINT notification is not currently used. It is assumed that if custom draw is enabled (by returning CDRF_SKIPDEFAULT in the pre-paint step), then the whole scrollbar will be custom-drawn, so you must draw all portions of a scrollbar if you want to draw any at all.

Removing cool scroll bars

If for some strange reason you want to turn off cool scrollbars, then simply call the UninitializeCoolSB API, passing the handle to the window. You do not need to call this function when your window is destroyed, as the cool scrollbars will automatically remove themselves in this instance.

A note about the source code

This library is written entirely in the C language, and uses no other library other than the standard win32 API. In order to encapsulate the code, it was necessary to package all of the implementation code into a single file, with all functions given static linkage so they don't conflict with other functions in your projects. This is a drawback of using C. It would be better to split the source into separate files which deal with specific scrollbar functions - such as drawing code in one file, mouse code in another and so on. However, this would require the use of namespaces to encapsulate the library, and namespaces are only available in C++. I thought that reaching a wider audience was more important in this case, but feel free to use your C++ compiler instead.

I should also take this opportunity to defend my use of C++ style comments in a C project. My reason: I find it tedious to use the C-style comments. Also, every C compiler for Windows that I tried accepts C++ comments with no problem. In fact, the new C99 standard allows the use of C++ style comments, but the project will compile fine on older compilers also. So there.

IMPORTANT:
There is a file called userdefs.h which can be used to include or exclude certain features of the cool scrollbar library. If you don't want support for inserted buttons, for example, then you can #undef the INCLUDE_BUTTONS definition, which will result in a smaller code size. There are many features which can be altered in this way, and they are all fully documented in this file.

Why you can't customize a standard Windows control

(Only relevant under Windows 95,98,ME)

Someone is bound to complain that they can't add cool scrollbars to a tree-view control, or an edit control, so I'll explain why this isn't possible.

Firstly, it is quite possible to custom-draw the scrollbars on a standard window. All you need to do is call InitializeCoolSB, after all. However, internally, the standard windows all make use of the standard scrollbar functions such as SetScrollInfo and SetScrollPos. This is a problem, because these functions cause a window's scrollbars to be re-drawn, and this redraw does not get performed via a WM_NCPAINT message. Whenever you resize a standard window, or scroll up and down in one, the standard scrollbar API will be called, and this will cause your nicely drawn custom scrollbars to be over-written. So, unless you have the source-code to a window or control, you cannot apply the cool scrollbar library to it, because you must call the CoolSB_xxx API functions to perform all scrolling operations instead.

Still not convinced? Well, in theory there is a way to overcome these problems and use the coolscrollbar library on any window, but it is not for the feint-hearted. It is possible to intercept any Windows API call in a program, and perform such tasks as replacing the function call with another user-defined call. For example, you could intercept all scrollbar API calls that your program made, and forward them on to the CoolSB_xxx versions instead.

There are loads of sources on the net which do API interception. These are the two techniques that I would look at:

  • Import Address Table (IAT) patching.
    This technique could be used with the common controls library, because the library contains a thunk table which contains pointers to the scrollbar functions which reside in USER32.DLL. The IAT can be re-written so that all scrollbar API calls get diverted to the CoolSB_xxx functions.
  • Binary re-writing of the target function.
    This is more complex, and involves over-writing the start of each scrollbar function actually inside USER32.DLL. You would have to use this technique if you wanted cool scrollbars on an edit or list control. This is because these controls are implemented inside USER32.DLL, and you can't patch the IAT for USER32, simply because it does not import its own API calls. So, you have to intercept the scrollbar API calls inside USER32 itself.

The new coolsb_detours library uses the second technique above to overcome this problem, but this will only work when your program runs under Windows NT. If you are using the Windows 9x family of operating systems, then your are on your own.

Conclusion

The Cool Scrollbar library is now in it's second release (version 1.1). Although I have addressed a couple of minor problems and introduced some new features, you must still be careful when you use this library. There might be bugs or incompatibilites with your projects which I have over-looked. I've tested the library thoroughly with my own projects, but it remains to be seen whether I've got it completely right. Feedback would be appreciated!

5 Jan 2002

Posted in Tutorials

win32
Powered by Drupal


Custom MessageBox | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
msgbox.zip

Published on 22 Jan 2012

3.39 Kb

Download

Custom MessageBox

How to customize the standard MessageBox dialog

This tutorial will show a simple technique for customizing a standard Windows message-box.

A standard message-box is not usually customizable, because you are never normally able to find it's window handle. This is because the MessageBox API call only returns control to a program when the message-box has been destroyed. Also, your program will never see any of the message-box window messages, because the MessageBox API call contains its own internal message-loop (much like the DialogBox API call).

{short description of image}

The image above shows how this technique has been used to modify the text of the standard OK button.

Window Hooks

The best way to intercept messages for a "message-box" is to install a hook. This is achieved by using the SetWindowsHookEx API call, available on all 32bit versions of Windows (9x, ME, NT, 2000, XP etc).

There are many different types of hook in Windows, each one designed for a different purpose. I don't want to review hooks in this tutorial, because there is plenty of information on MSDN. Just do a search for SetWindowsHookEx and you will find plenty of resources.

The only thing I will say about hooks is this: Windows hooks will inevitably slow your program down. It is therefore desirable to choose a hook that is called for as few events as possible, but which still gives us sufficient flexiblity to perform our customization. At the one end of the spectrum are the WH_CALLWNDPROC and WH_GETMESSAGE hooks. Hooks of this type will be called whenever a message is sent to a window. This will obviously add a significant overhead to any application, so unless you specifically need to trap all messages, hooks of this type are best avoided.

At the other end of the spectrum is the WM_CBT hook (Computer Based Training hook). This hook is called for very few window messages - specifically, window activation (WM_ACTIVATE), window creation (WM_CREATE / WM_DESTROY), window sizing (WM_SIZE / WM_MOVE) plus a few others. This type of hook is ideal for our purposes, because it will add little overhead (it will only be called occasionally), and it also provides a method for customizing a window: namely, during window creation.

Our strategy will be this:

  1. Install a CBT hook.
  2. Call MessageBox as normal.
  3. Remove the hook.

It's that simple!

Install a hook

Installing a Windows hook is really easy. The following code shows how.

?
HHOOK hMsgBoxHook = SetWindowsHookEx(
        WH_CBT,                // Type of hook
        CBTProc,               // Hook procedure (see below)
        NULL,                  // Module handle. Must be NULL (see docs)
        GetCurrentThreadId()   // Only install for THIS thread!!!
        );

It is important to highlight the call to GetCurrentThreadId. It is possible to install a system-wide hook by specifying the module-handle of a DLL in which the hook procedure resides. Whenever an hooked event occurs in another process, Windows automatically loads this DLL into that process, which then enables the hook procedure to be executed in that process's address space. This is obviously quite an overhead, and is a feature that we do not require. To avoid this happening, we can specify a NULL module handle, and just install the hook for the current thread.

Removing a hook

Removing a hook is even simpler than installing it. Just call UnhookWindowsHookEx and your done!

?
UnhookWindowsHookEx(hMsgBoxHook);

The hook procedure

The one thing I havn't shown yet is the actual hook procedure, that is called by Windows each time a CBT event occurs (i.e. whenever a WM_ACTIVATE / WM_CREATE message is sent to your application).

A CBT hook procedure needs to look something like this:

?
LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    HWND hwnd;
    if(nCode < 0)
        return CallNextHookEx(hMsgBoxHook, nCode, wParam, lParam);
    switch(nCode)
    {
    case HCBT_ACTIVATE:
        // Get handle to the message box!
        hwnd = (HWND)wParam;
        // Do customization!
        return 0;
    }

    // Call the next hook, if there is one
    return CallNextHookEx(hMsgBoxHook, nCode, wParam, lParam);
}

Bringing it all together

To tidy this all up, we can write a simple wrapper function around MessageBox, which hides the hook details. Whenever you want to display a custom MessageBox, just use this function instead.

?
static HHOOK hMsgBoxHook;
...
int MsgBoxEx(HWND hwnd, TCHAR *szText, TCHAR *szCaption, UINT uType)
{
    int retval;
    // Install a window hook, so we can intercept the message-box
    // creation, and customize it
    hMsgBoxHook = SetWindowsHookEx(
        WH_CBT,
        CBTProc,
        NULL,
        GetCurrentThreadId()            // Only install for THIS thread!!!
        );
    // Display a standard message box
    retval = MessageBox(hwnd, szText, szCaption, uType);
    // remove the window hook
    UnhookWindowsHookEx(hMsgBoxHook);
    return retval;
}

Hopefully this tutorial has given you information for you to customize your own message-boxes. How you decide to do the customization is up to you - the technique described here takes you part of the way, but it is up to you to take it further.

Here are a few ideas for how you might customize a MessageBox:

  • Use CreateWindow(...) to insert a check-box at the bottom of the MessageBox window.
  • Modify the button-text of one of the buttons.
  • Change the icon image.

Have fun!

30 Jun 2002

Posted in Tutorials

win32
Powered by Drupal


System Image List | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
sysimg.zip

Published on 22 Jan 2012

13.27 Kb

Download

System Image List

How to access the System Image List (AKA the Shell Icon Cache)

Chinese translation at www.titilima.cn

Introduction

The System Image List (or Shell Icon Cache as it is sometimes called) is an icon resource maintained by the Windows Shell. This list is used by Explorer, among other applications, to display the icons for system objects, programs and document types.

{short description of image}

The list is no more than a simple HIMAGELIST (a standard Imagelist, which can be accessed using the standard image list API). Some applications may find it useful to display system supplied icons, rather than storing their own duplicate icons internally. So, the aim of this tutorial is to demonstrate how to access the System Image List and use it within your own applications.

Simple method first

If you've ever spent any time browsing around the MSDN help system, you may have stumbled over the SHGetFileInfo API call (exported from shell32.dll, available in Win9x and WinNT4/2000/XP). The function looks like this:

?
DWORD_PTR SHGetFileInfo(
    LPCTSTR pszPath,
    DWORD dwFileAttributes,
    SHFILEINFO *psfi,
    UINT cbFileInfo,
    UINT uFlags
);

This function call can be used to get information for a specified file. The uFlags parameter specified what information is desired. Two particularly interesting values for this parameter are SHGFI_ICON and SHGFI_SYSICONINDEX. When these flags are specified, SHGetFileInfo returns a handle to the system imagelist, and stores the index of the requested icon type in the SHFILEINFO structure.

The following call demonstrates how this is possible:

?
SHFILEINFO shfi;
HIMAGELIST hSysImgList;
...
hSysImgList = (HIMAGELIST)SHGetFileInfo(
       "C:\",
       0,
       &shfi,
       sizeof(SHFILEINFO),
       SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_ICON);

Platform differences

There is a subtle, but important difference between Windows 9x and Windows NT family of operating systems. Under Windows 9x, SHGetFileInfo returns the handle to the system imagelist, which is shared by all processes. This imagelist is the same one used by Explorer and the Shell to display the system icons. That means that if one process corrupts the imagelist in any way, all processes (this includes Explorer) will be affected.

Under Windows NT (and 2000/XP) things are slightly different. Due to the system architecture, and also to maintain a robust operating environment, SHGetFileInfo will return a separate copy of the system imagelist to each process that requests it (one for each process). What's more, SHGetFileInfo does not return the whole system imagelist like it does with Windows 9x - only those icons requested in the call to SHGetFileInfo will be present in the imagelist - all the other entries are left blank. For 99% of situations this does not matter, but because we want to make a viewer application to view the whole imagelist, this presents us with a problem.

Locating the System Imagelist on all versions of Windows

I'm not going to beat around the bush. There is an undocumented API call inside Shell32.DLL called Shell_GetImageLists, which - you guessed it - returns a handle to the system imagelists (note the plural here - there are large and small icon versions). This undocumented API call is available on all versions of Windows that ship with shell32.dll.

?
BOOL WINAPI Shell_GetImageLists(HIMAGELIST *lphimlLarge, HIMAGELIST *lphimlSmall);

The one other API we need is FileIconInit, which is only available under Window NT/2000/XP. This is not a problem, because we only need to call is under Windows NT anyway.

?
BOOL WINAPI FileIconInit(BOOL bFullInit);

These APIs are not exported by name, only by ordinal:

?
; Shell32 undocumented functions
Shell_GetImageLists  @71
FileIconInit         @660

Armed with this information, we can now write a function to return the handles to the system imagelist. The first step is to make a couple of typedef's to make our lives easier.

?
typedef BOOL (WINAPI * SH_GIL_PROC)(HIMAGELIST *phLarge, HIMAGELIST *phSmall);
typedef BOOL (WINAPI * FII_PROC)   (BOOL fFullInit);

SH_GIL_PROC is short for Shell_GetIconLists_Procecedure, FII_PROC is short for FileIconInit_Procedure. Now we can locate the undocumented functions from inside Shell32.DLL so that we can call them:

?
HMODULE hShell32;
SH_GIL_PROC  Shell_GetImageLists;
FII_PROC     FileIconInit;
// Load SHELL32.DLL, if it isn't already
hShell32 = LoadLibrary("shell32.dll");
if(hShell32 == 0)
    return FALSE;
//
// Get Undocumented APIs from Shell32.dll:
//
Shell_GetImageLists  = (SH_GIL_PROC) GetProcAddress(hShell32, (LPCSTR)71);
FileIconInit         = (FII_PROC)    GetProcAddress(hShell32, (LPCSTR)660);

Note how the ordinals are simply passed as parameters to GetProcAddress. OK, now we have pointers to the functions we need we can proceed:

?
// Initialize imagelist for this process - function not present on win95/98
if(FileIconInit != 0)
    FileIconInit(TRUE);
// Get handles to the large+small system image lists!
Shell_GetImageLists(phLarge, phSmall);
// Do NOT free shell32.dll!

The last comment in the snippet above says "Do NOT free shell32.dll". This is important, because as soon as shell32.dll is unloaded, the system-image lists are destroyed, undoing all our hard work.

So, that's it! We now have handles to the large and small system imagelists, which we can display using the sample viewer application provided (see the link at the top of the page).

Never modify or delete the System Image List!

There is one more important piece of information that you should know before we go any further. You should never try to add icons to, remove icons from, or delete the System Imagelist. This is explained clearly in the documentation for SHGetFileInfo, and very good advise this is, too. However, there is one very easy way to accidently delete the System Imagelist.

One very common practise is for an application to create a TreeView or ListView, to display the names and details of files in the system (like Explorer, or WinZip for example). In order to display the system icons next to each entry in a TreeView / ListView, an application typically uses, for example, the LVM_SETIMAGELIST message (or the ListView_SetImageList macro) to assign an imagelist to the control. So, it would be entirely feasible for a program to obtain a handle to the system imagelist, and assign it to a control to use as it's own icon list. In fact, this is very probably how Explorer works.

Now for the problem. By default, when a ListView control is destroyed, it will automatically delete it's associated image list using the ImageList_Destroy API call. That's right, by default your program will obliterate the system imagelist without you even asking it to! Fortunately, there is a ListView control style (LVS_SHAREIMAGELISTS) which prevents a ListView control from deleting it's imagelist. (Make sure you set this style if you are in this situation!).

Mysteriously, the TreeView control does not suffer this problem - MSDN states that a TreeView will not destroy it's associated imagelist, so only the ListView control needs special attention.

Obviously, if your program runs under Windows NT/2000/XP, accidently deleting the system imagelist is not very serious, because you are only deleting your own process's copy of the imagelist. However, under Windows 95/98/ME, deleting the imagelist will have disasterous consequences. Well, just try it and see!

Conclusion

Obtaining the complete system imagelist could have been tricky, especially if we had had to resort to hacking the icons directly from the cached disk file they are stored in. Fortunately, there are a couple of undocumented API calls which came to the rescue. Of course, I can't claim credit for discovering these - I found this information on James Holderness's excellent website, which has more information on the sytem imagelist than I presented here, as well as loads more undocumented goodies! Check it out now!

Have fun!

2 Jul 2002

Posted in Tutorials

win32
Powered by Drupal


Custom Combobox | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
flatcombo.zip

Published on 22 Jan 2012

5.47 Kb

Download

Custom Combobox

How to customize the appearance of a combobox.

Introduction

It's time for a new tutorial, something that has been overdue for a long time. The first new tutorial I want to write is going to explore how to implement a flat combo-box. The same technique will also be suitable for making combo-boxes with customized borders as well.

Most of the tutorials I've written so far (which concerned changing a window's appearance) did so by modifying a window's non-client area. This is a nice way of doing this type of thing because the window's frame is separated from the content it is displaying. Any custom-control that you may also decide to write should also follow this same design principle - keep the window borders in the non-client area, and the user-data inside the client area.

The ComboBox is different because it does not have a non-client area, and hence has no "real" window borders. The ComboBox's 3D appearance is achieved by painting the entirety of the control within it's client area. This means that it will be extremely difficult to completely remove the borders from the control. Whichever Microsoft employee who decided to design the ComboBox in this manner should be ashamed of themselves, quite frankly, because every other control within Windows follows the same design-rules, except the ComboBox.

Subclassing a ComboBox

Fortunately I know of a nice way to modify a ComboBox's appearance which is very suited to what we want to do. Yet again we will need to subclass the ComboBox window and intercept one or two window-messages to add the functionality we need.

The first step is therefore to perform the subclassing, this is exactly the same as for any other window:

?
VOID MakeFlatCombo(HWND hwndCombo)
{
    LONG OldComboProc;

    // save the current window procedure before we subclass
    OldComboProc = GetWindowLong(hwndCombo, GWL_WNDPROC);
    SetWindowLong(hwndCombo, GWL_USERDATA, OldComboProc);
    // subclass the window procedure
    SetWindowLong(hwndCombo, GWL_WNDPROC, (LONG)FlatComboProc);
}

Subclassing a window is easy and I've covered this many times before so there's no need to explain any further. The next step is to now write a replacement window procedure which preserves the combo-box's original functionality, but also removes it's 3D-borders from the client area.

The method I have used to achieve this is actually a bit of a cheat, because we are not going to remove the 3D borders. Instead we will leave the 2-pixel area around the control intact, but we'll make it look different by painting it in a solid colour to make it look flat.

?
LRESULT CALLBACK FlatComboProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    // get the previous window procedure so we can still call it
    WNDPROC OldComboProc = (WNDPROC)GetWindowLong(hwnd, GWL_USERDATA);

    switch(msg)
    {
    case WM_PAINT:
        // custom painting will go here
    }
    // preserve original window's functionality
    return CallWindowProc(OldComboProc, hwnd, msg, wParam, lParam);
}

Above is the very simplest form of subclass possible. This replacement window-procedure simply uses CallWindowProc to call the old window procedure. Our custom painting logic will be implemented inside the WM_PAINT handler. Note that this is different to previous tutorials which always used WM_NCPAINT - this is because we are going to modify the client area of the combo-box.

Changing a ComboBox's client area

OK, the boring bit is out of the way. The only thing left to do is modify the painting operation of a combo box.

?
HDC hdc;
RECT         rect;
PAINTSTRUCT  ps;
hdc = BeginPaint(hwnd, &ps);
// Find coordinates of client area
GetClientRect(hwnd, &rect);
// Deflate the rectangle by the size of the borders
InflateRect(&rect, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(CM_CYEDGE));
// Make a mask from the rectangle, so the borders aren't included
IntersectClipRect(hdc, rect.left, rect.top, rect.right, rect.bottom);
// Draw the combo-box into our DC
CallWindowProc(OldComboProc, hwnd, msg, (WPARAM)hdc, lParam);

Let's walk through the code line-by-line. The first thing we have to do is obtain a Device Context to draw into, this is obtained using the standard BeginPaint API call. The next step is to work out the coordinates of the combo-box area that we want to draw - i.e. the region not including the outer borders. This is obtained using a combination of GetClientRect and InflateRect. The final stage is to call the original window procedure to paint the combo-box normally, but before we do this we make sure that the "normal" borders don't get drawn by masking them out with the IntersectClipRect call.

It's actually pretty simple. The only thing left to do now is to draw our own custom border around the control, otherwise this area won't ever get updated and will appear "dirty".

?
// Remove the clipping region
SelectClipRgn(hdc, NULL);
// now mask off the inside of the control so we can paint the borders
ExcludeClipRect(hdc, rect.left, rect.top, rect.right, rect.bottom);
// paint a flat colour
GetClientRect(hwnd, &rect);
FillRect(hdc, &rect, GetSysColorBrush(COLOR_3DSHADOW));
EndPaint(hwnd, &ps);

The code above is very simple. All it does is invert the clipping rectangle so that instead of the borders being masked off and the inside being "exposed", the inside is masked off and the borders are exposed. The borders are then painted a dark colour to show that they have been customized. You have two options here really. To remove the appearance of the borders altogether you should choose a colour that blends into the combo's parent window. For example, if the combo box was in a dialog box you would paint the combo's borders the same colour as the dialog background. The alternative is to make your own custom 3D effect, again this is totally up to you how you decide to do this.

Making a custom button

The difficult part is out of the way. The rest of this tutorial is now only concerned with additional effects for the combo box. The next logical step for combo-box customization is to customize the appearance of the 3D button on the right of the control. Again, it is not possible to remove the button, only to change it's appearance to something else.

This stage is very simple, and only requires one extra line of code to what we've already got. By simply adding the button's region to the clipping rectangle when we do the painting we can prevent the button from being drawn "normally" in the exact same way we did for the 3D borders.

?
// Find coordinates of client area
GetClientRect(hwnd, &rect);
// Deflate the rectangle by the size of the borders
InflateRect(&rect, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(CM_CYEDGE));
// Remove the drop-down button as well
rect.right -= GetSystemMetrics(CM_CXSCROLL);

As you can see only one line as been added - an adjustment of the right-hand-side of the clipping rectangle so that it excludes the drop-down button.

Conclusion

I'm going to stop here because the important bits have been done. The source-code download does a little more work than I've presented here and shows how to draw a completely custom combo-box which also responds to mouse-messages correctly. Hopefully this tutorial has answered any questions you might of had regarding ComboBox customization. Stay tuned for more tutorials coming soon!

4 Jan 2004

Posted in Tutorials

win32
Powered by Drupal


64bit Scrollbars | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

64bit Scrollbars

How to create 64bit scroll ranges using standard Win32 scrollbars

Recently I have been rewriting part of my HexEdit application. One specific area was to properly handle 64bit file sizes (i.e. files that were over 4Gb in size). The problem I faced was not the loading of files this large, but the issue of initializing Win32 scrollbars with 64bit values. Seeing as I found a nice elegant solution to the problem I thought I would share the results of my work.

Scrollbars in Windows

Scrollbars have always been an integral part any GUI-based application. However in Windows operating systems the supported scrollbar range has always been tied to the native WORD width of the OS - for example Windows 3.1 (a 16bit OS) used 16bit quantities to represent scrollbar metrics. This provided applications with a scrollbar range of -32768 to 32767.

When Microsoft introduced their win32 API model they extended the standard scrollbar range to use LONG values whilst still maintaing compatibility with the older 16bit model. This allowed a scrollbar's to maximum range to increase -2147483648 to 2147483647. This is still way short of the maximum value a 32bit unsigned integer can hold, but for most applications this scrollbar range is more than satisfactory.

Although uncommon, there are some applications which require a scrolling range beyond that which Windows natively provides. Extending a scrollbar's range to a 64bit number system would be very useful especially for hex-editors.

Fortunately Microsoft Visual C++ has a built-in integer type which can represent 64bit numbers - the __int64 type. An unsigned __int64 (or UINT64) variable can hold a maximum value of 18,446,744,073,709,551,615. That's a pretty big number (18 Exa-bytes), considering the the maximum value a 32bit unsigned integer can hold is 4,294,967,295 (4 Giga-bytes).

It is fairly obvious that a 64bit number will not fit into a 32bit variable so the solution is to maintain each scrollbar's range in "external" 64bit variables and scale these numbers down to 32bits whenever we want to access a scrollbar's settings.

Managing scrollbar variables

As a programmer you don't need to do anything different when dealing with 64bit scrollbar quantities. Most programs will already maintain scrollbar state so the only thing that changes is the size of the variables (32bit -> 64bit) .

To keep things simple we ignore the fact that a scrollbar has a "minimum" value and will make the scrollbar range zero-based - this makes the maths later on much easier. So the only values we need to keep track of are the scrollbar's position, and it's current maximum value:

?
UINT64 nScrollPos64;
UINT64 nScrollMax64;

Of course these variables would normally be stored as C++ class members but thats not really important for this example. Below shows a typical scrollbar sequence of code.

?
void OnVScroll(HWND hwnd, int nSBPart)
{
    switch(nSBPart)
    {
    case SB_TOP:
        nScrollPos64 = 0;
        break;
    case SB_BOTTOM:
        nScrollPos64 = nScrollMax64;
        break;
    case SB_LINEUP:
        nScrollPos64--;
        break;
    case SB_LINEDOWN:
        nScrollPos64++;
        break;
    case SB_THUMBTRACK:
        nScrollPos64 = GetScrollPos64(hwnd, SB_VERT, nScrollMax64);
        break;
    }
    SetScrollInfo64(hwnd, SB_VERT, SIF_ALL, nScrollMax64, nScrollPos64, nScrollPage, TRUE);
}

If you look at the code above you will see there are two function calls highlighted in bold - GetScrollPos64 and SetScrollInfo64. Of course, normally a program would call the "normal" Win32 GetScrollPos and SetScrollInfo but we must do things a little differently if we are to emulate 64bit scrollbar ranges.

What I have done is to create an interface between our 64bit scrollbar variables and the win32 scrollbar APIs. These two functions act just like the win32 API - and hide the complexity of the 32bit/64bit conversions that must be managed. The idea is to provide a mechanism to support 64bit scrollbars without having to change any scrolling logic.

Scaling a scrollbar's range

As mentioned above we must maintain our "64bit" scrollbar range separately from the Windows scrollbar. The relationship between the two scroll ranges can be expressed using a simple mathematical formula. For convenience's sake I will refer to these two ranges using four separate variables - pos32, max32 (for 32bit scrollbar ranges), and pos64, max64 (for 64bit ranges).

Recall that previously I mentioned there were two new functions to be written - GetScrollPos64 and SetScrollInfo64. What you must understand is this:

There are really only two tasks that are commonly performed with scrollbars. The first is to set the scrollbar thumb position programmatically. The second is to retrieve the scrollbar thumb position when it has been modified (dragged) by the user.

Therefore to set the scrollbar position we must call one of the Win32 scrollbar APIs - SetScrollInfo is the most common for 32bit programs. Before we call SetScrollInfo we must calculate the real (32bit) thumb position by performing some kind of mathemetical operation. By using simple rearrangement of the original formula we can derive our first equation:

It really is that simple! Now onto the second task, which involves retrieving the 64bit scrollbar thumb position using the Win32 GetScrollInfo. The rearrangement below gives us our second equation.

In both of these equations we always know what value two of the variables will be- but max32 is always an unknown so we can't solve the equations yet. It actually doesn't matter what this value is, as long as we set it to some known quantity in order to solve the equations. MAX32 will therefore be defined as a constant integer value (something like 0x7ffffffff). With all three variables now known, we can solve both equations. It's time to put this into code...

SetScrollInfo64 first steps

So our first task will be to implement a function which can set a scrollbar's range, given two 64bit values and a 32bit constant. In order to work out what to set the scrollbar position to, we use the first equation:

Now before we rewrite this equation in "C" notation you should understand the following concept:

In mathematics, it doesn't matter what order the equation is evaluated in. We could perform the multiplication first then the division, or we could divide first of all then multiply up. It doesn't matter because real maths uses real numbers.

This idea is illustrated below but in "C" notation:

?
pos32 = (pos64 * MAX32) / max64;
pos32 = (pos64 / max64) * MAX32;

The first equation is traditionally the preferred "programmatic" method because there will be less loss of precision. This is because the integer division operation on a CPU will always lose precision - by the very nature of how integer arithmetic works:

By performing the division first of all (the second method) there is the potential to lose accuracy when pos64 and max64 are close in value - and when the multiplication is subsequently performed it is using inaccurate numbers. The first method does not have this problem because we multiply first of all (maintaining precision) and only lose precision at the very end resulting in a more accurate answer. This is a common approach in programming which you are hopefully aware of.

Now ordinarily we would opt for method#1, but there is a problem with this approach. Because we want to use the full range that the 64bit numbers can represent, we will suffer from integer overflow when pos64 and max32 are both very large. We could theoretically use 128bit integers to hold the numbers whilst we calculate the expression but this would be very difficult without built-in compiler support.

At this point we might have got stuck - but we haven't finished rearranging our equation yet. If we divide the numerator and demoninator by max32 we end up with:

?
pos32 = (pos64 * MAX32 / MAX32) / (max64 / MAX32)

This can be simplified and our original formula can now be re-written as:

In "C" notation this looks like:

?
pos32 = pos64 / (max64 / MAX32)

Now I realise that this probably doesn't look like the ideal way to solve this problem using integer maths (we have two divisions now) but I have a trick up my sleeve. With this third rearrangement we can control the accuracy of the division operator - because we control the value of max32.

When max64 is very large the result of the division is quite accurate (in integer terms!) - i.e., when max64 is a fair bit larger than max32. However when max64 approaches max32 in value the result will become very inaccurate, as we already know. The trick is to never allow these values to get this close together.

Because we control what max32 will be it is natural to set it to the maximum value that a 32bit scrollbar can hold - 0x7fffffff (2147483647). But there is no reason why we couldn't just choose a much lower number - for example 0x7fff (32767, the maximum value of a 16bit scrollbar!).

Remember that max32 is the value we give the scrollbar maximum range using SetScrollInfo, and that I am now proposing that we set this maximum value to 32767 - far less than the maximum number a 32bit integer can hold. The thing is, it really doesn't matter what we set the scrollbar maximum to. We are scaling number ranges anyway so we will never be 100% accurate - and anyway, this scroll range still has to get translated even further when Windows displays the scrollbars on screen. The point is, noone will ever notice.

The original problem still remains though - when max64 this time approaches 32767 (i.e. 32768) we still lose precision. However we are now in the position where we can prevent this from happening. The way we do this is simple: when max64 happens to be in the range 0x7fffffff to 0x7fff (i.e. within the range of a regular 32bit number) we can simply revert to the normal Win32 API because all our numbers will be 32bits!!

?
if(max64 <= WIN32_SCROLLBAR_MAX)
{
    pos32 = pos64;
}
else
{
    pos32 = pos64 / (max64 / WIN16_SCROLLBAR_MAX);
}

Simple, no? Well I think so anyway. Anyway let's define these two "maximum" limits as follows:

?
#define WIN16_SCROLLBAR_MAX 0x7fff
#define WIN32_SCROLLBAR_MAX 0x7fffffff

Now we still don't have to use these exact numbers - as long as we choose two numbers which are far enough apart (2147450880 in this case!!) we can ensure that our sums will always be very accurate.

SetScrollInfo64

We can now write our first replacement scrollbar API, SetScrollInfo64. Before you cast your eyes down, be aware that the names of the variables we are used to (pos32, max32, max64 etc) have now changed to be more meaningful for a Win32 program.

?
//
// Wrapper around SetScrollInfo, performs scaling to
// allow massive 64bit scroll ranges
//
BOOL SetScrollInfo64( HWND hwnd,
                      int nBar,
                      int fMask,
                      UINT64 nMax64,
                      UINT64 nPos64,
                      int nPage,
                      BOOL fRedraw
                    )
{
    SCROLLINFO si = { sizeof(si), fMask };
    // normal scroll range requires no adjustment
    if(nMax64 <= WIN32_SCROLLBAR_MAX)
    {
        si.nMin  = (int)0;
        si.nMax  = (int)nMax64;
        si.nPage = (int)nPage;
        si.nPos  = (int)nPos64;
    }
    // scale the scrollrange down into allowed bounds
    else
    {
        si.nMin  = (int)0;
        si.nMax  = (int)WIN16_SCROLLBAR_MAX;
        si.nPage = (int)nPage;
        si.nPos  = (int)(nPos64 / (nMax64 / WIN16_SCROLLBAR_MAX));
    }
    return SetScrollInfo(hwnd, nBar, &si, fRedraw);
}

This function doesn't look exactly the same as the regular SetScrollInfo because we are passing all of the scrollbar metrics as parameters rather than using a separate SCROLLINFO structure - or SCROLLINFO64 as it would have to be called. I think it's neater this way so I've left it as it is.

GetScrollPos64

The second task (calculating the 64bit scrollbar position) is somewhat simpler because we (hopefully!) understand the issues involved now.

Writing this equation in "C" notation yields the following:

?
pos64 = (pos32 * max64) / MAX32

Of course we have the overflow problem again with the multiplication, but we now know that the division operation will not produce the inaccuracy we fear because we can control the values of max64 and max32 just like before. We can therefore safely change the order of evaluation as shown below.

?
pos64 = pos32 * (max64 / MAX32);

One thing that we must be careful of is the situation where the scrollbar thumb is right at the very end of its range (i.e. the thumb position is equal to the maximum position). The reason is that integer math always rounds down. Even when the scroll-thumb was at it's maximum value, there would be alot of cases when pos64 would never equal max64. Therefore I have a special case for this end-of-scroll-range and force pos64 to be equal to max64 in this situation.

?
//
// Wrapper around GetScrollInfo, returns 64bit scrollbar position
// fMask must be either SIF_POS or SIF_TRACKPOS
//
size_w GetScrollPos64( HWND hwnd,
                       int nBar,
                       int fMask,
                       UINT64 nMax64)
{
    SCROLLINFO si = { sizeof(si), fMask | SIF_PAGE};
    UINT64 nPos32;
    if(!GetScrollInfo(hwnd, nBar, &si))
        return 0;
    nPos32 = (fMask & SIF_TRACKPOS) ? si.nTrackPos : si.nPos;
    // special-case: scroll position at the very end
    if(nPos32 == WIN16_SCROLLBAR_MAX - si.nPage + 1)
    {
        return nMax64 - si.nPage + 1;
    }// normal scroll range requires no adjustment
    else if(nMax64 <= WIN32_SCROLLBAR_MAX)
    {
        return nPos32;
    }
    // adjust the scroll position to be relative to maximum value
    else
    {
        return nPos32 * (nMax64 / WIN16_SCROLLBAR_MAX);
    }
}

For this second function we require the scrollbar's nPage as part of the calculations - so have obtained it from the scrollbar rather than passing it in as a parameter. This is just for neatness as usually you wouldn't want to worry about this small detail - but be aware that it is happening.

How to use this new API

It really isn't any different than what you would do normally!

?
void example(HWND hwnd)
{
    UINT64 max = 0xffffffffffffffff;
    UINT64 pos = 0x1234567812345678;
    SetScrollInfo64(hwnd, SB_VERT, SIF_ALL, max, pos, 15, TRUE);
    // when reacting to a WM_VSCROLL, with SB_THUMBTRACK/SB_THUMBPOS:
    pos = GetScrollPos64(hwnd, SB_VERT, SIF_TRACKPOS, max);
}

Possible optimization yields bad results

I mentioned earlier that the numbers we used for max32 and max64 didn't have to be the exact values I chose.

You could in fact set max32 (WIN16_SCROLLBAR_MAX as it is now known) to be a power-of-2 (i.e. 0x8000, just 1 more than it is now). This would eliminate one of the divisions because we could use bit-shifts instead. The reason I stuck with a divide-by-0x7ffff rather than a divide-by-0x8000 (a right-shift-by 15) is this:

I designed these functions for use in a Hex Editor. If we used a right-shift instead of a division the result would be that the scrollbar positions would not be very granular. Every time we scrolled the scroll-thumb up and down, the resulting 64bit position would always be a multiple of 0x8000 (32768). It would look like there is a loss of precision and make the address-column in a hex editor look very "sticky". A divide by 0x7fff (32767) yields much better randomness of the bits and the result is more "natural".

If you decide that this apparent loss of precision is not a problem then go ahead and optimize all you like :-)

Conclusion

You may be wondering why I bothered doing all this work when I could have used floating point arithmetic. Here's why.

  • It isn't necessary!
  • Floating point arithmetic in VC++ cannot represent the largest 64bit integer numbers accurately. I couldn't be bothered to work out if this would be an issue or not.
  • Floating point maths requires the use of extra runtime support and introduces bloat that we don't need.
  • I consider floating point math dirty and try to avoid it unless the benefits of simplicity outweigh that dirty, sordid feeling you get when you hit that compile button, knowing that those filthy doubles will be corrupting your sleek integer-only program somehow.
  • Did I mention that I don't like floating point? :-)

Hopefully you have found this tutorial useful, or at least interesting as you may not have any immediate use for this subject. Anyway as before I'd appreciate any feedback you may have.

2 Sep 2005

Posted in Tutorials

win32
Powered by Drupal


Using cards.dll API | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
cards.zip

Published on 22 Jan 2012

7.13 Kb

Download

Using cards.dll API

How to access the cards.dll API

The aim of this article is to describe exactly how to use cards.dll, the dynamic link library that has shipped with all versions of windows since version 3.0. This library contains all of the playing card bitmapped images that games such as Windows Solitare use to display their graphics.

There are actually two versions of cards.dll. The Windows 95 series of operating systems (this includes Windows 98 and Windows ME) use the original 16bit version which shipped way back with Windows 3.0. Obviously there are going to be problems using this 16bit DLL from your 32bit application, but I will come on to that later. The second version is a 32bit DLL, and ships with the Windows NT series (Windows NT, Windows 2000 etc). I will explain how to use this version first.

Exported functions from cards.dll

Cards.dll exports five functions which your program can use to draw card images. These functions are the the only interface that cards.dll provides to draw cards. Note that these functions never let you have access to the bitmaps; rather, you supply a device context and the card functions draw the card bitmaps for you. Here is a description of each function, and its prototype.

cdtInit

?
BOOL WINAPI cdtInit (int *width, int *height)

Initializes the cards.dll library for your application. You must supply the addresses of two variables, in which cards.dll stores the width and height (in pixels) of a card.

cdtDraw

?
BOOL WINAPI cdtDraw (HDC hdc, int x, int y, int card, int type, DWORD color)

Draws a card at position x, y on the device context hdc. The type parameter controls whether the front, the back, or the inverted front of the card is drawn.

The card parameter controls which card is drawn. This parameter is dependent on the value of type. If a card face is to be drawn (type is 0 or 2), then card must be a value from 0 through 51 to represent each card. If type specifies that a card back is to be drawn (type is 1), then card must be a value from 53 to 68 (inclusive), to represent one of the 16 possible card backs.

The card faces are organised in increasing order. That is, the aces come first, then the two's and so on. In each group, the cards are ordered by suit. The order is clubs, diamons, hearts, spades. This pattern is repeated as the card values increase.

You can use the following values to represent each card suit.

?
#define ecsCLUBS    0
#define ecsDIAMONDS 1
#define ecsHEARTS   2
#define ecsSPADES   3

To calculate the value of card given a suit (0-3) and a face value (0-13, 0 being aces, 13 being kings), you can use this simple formula:

?
card = suit + face * 4

Here is a list of the 16 possible card backgrounds that you can use in your code.

?
#define ecbCROSSHATCH  53
#define ecbWEAVE1      54
#define ecbWEAVE2      55
#define ecbROBOT       56
#define ecbFLOWERS     57
#define ecbVINE1       58
#define ecbVINE2       59
#define ecbFISH1       60
#define ecbFISH2       61
#define ecbSHELLS      62
#define ecbCASTLE      63
#define ecbISLAND      64
#define ecbCARDHAND    65
#define ecbUNUSED      66
#define ecbTHE_X       67
#define ecbTHE_O       68

Lastly, the color parameter sets the background color for the ecbCrossHatch card back, which uses a pattern drawn with lines. All the other backs and fronts are bitmaps, so color has no effect.

cdtDrawExt

?
BOOL WINAPI cdtDrawExt (HDC hdc, int x, int y, int dx, int dy,
    int card, int suit, DWORD color)

This procedure is the same as cdtDraw except that you specify the dx and dy parameters to indicate the size of the card. The card bitmaps are stretched or compressed to the specified size.

cdtAnimate

?
BOOL WINAPI cdtAnimate (HDC hdc, int cardback, int x, int y, int frame)

This function animates the backs of cards by overlaying part of the card back with an alternative bitmap. It creates effects: blinking lights on the robot, the sun donning sunglasses, bats flying across the castle, and a card sliding out of a sleeve. The function works only for cards of normal size drawn with cdtDraw. To draw each state, start with frame set to 0 and increment through until cdtAnimate returns 0.

cdtTerm

?
void WINAPI cdtTerm (void)

This function cleans up the card resources from your program. It takes no parameters, and returns no values. It is a good idea to call this function just before your card game exits.

How to load a Dynamic Link Library

The best way to access the cards.dll libary is to use the LoadLibrary API call, and then use GetProcAddress to access the individual functions that cards.dll exports.

?
HMODULE hCardDll;
...
BOOL InitCardsDll(void)
{
    hCardDll = LoadLibrary("cards.dll");
    if(hCardDll == 0)
        return FALSE;
    cdtInit   = (pfcdtInit)   GetProcAddress(hCardDll, "cdtInit");
    cdtDraw   = (pfcdtDraw)   GetProcAddress(hCardDll, "cdtDraw");
    cdtDrawEx = (pfcdtDrawEx) GetProcAddress(hCardDll, "cdtDrawExt");
    cdtTerm   = (pfcdtTerm)   GetProcAddress(hCardDll, "cdtTerm");
    return TRUE;
}

You should use the following typedef's to define each function prototype:

?
typedef BOOL (WINAPI *pfcdtInit)(int *, int *);
typedef BOOL (WINAPI *pfcdtDraw)(HDC, int x, int y, int card, int type, DWORD color);
typedef BOOL (WINAPI *pfcdtDrawEx)(HDC, int x, int y, int dx, int dy, int card, int type, DWORD color);
typedef BOOL (WINAPI *pfcdtAnimate)(HDC hdc, int cardback, int x, int y, int state);
typedef void (WINAPI *pfcdtTerm) (void);

Note that all parameters to the 32bit library are themselves 32bits in size.

That's really all there is to it. Don't forget to release the library with FreeLibrary before your program exits.

Using the 16bit cards.dll library

If you want your card game to run on Windows 95 then you have a problem, because 16bit DLLs are not directly compatible with a 32bit application. You have two options to choose from. The first is to distribute the 32bit version of cards.dll with your game. This is a bad idea, because firstly it's probably illegal to do this unless you have permission from Microsoft, and secondly, cards.dll is around 150Kb, which is a bit on the hefty side. Your second option is to access the 16bit dll from your 32bit code using a technique called Flat Thunking.

Flat Thunking is documented (I won't say fully) in MSDN. It involves writing two additional thunking DLLs; a 32bit DLL which thunks down to 16bit mode, and a 16bit DLL which calls the functions inside the "target" 16bit DLL.

Whilst this is OK in itself, I've always thought it was a little messy to have to distribute two additional DLLs. Fortunately there is an undocumented method to perform a Flat Thunk directly from your win32 application.

There is an undocumented function in KERNEL32.DLL called QT_Thunk. This function is used by a flat-thunk DLL pair to perform a thunk from 32 to 16bits. Using a little assembly language we can use this function directly from our own application without any additional DLLs. I won't describe here exactly how to perform this trick as it is fully documented in the source code.

Assuming that we can now access the 16bit version of cards.dll, there are a couple of points to mention. The first is that we are dealing with a 16bit executable module. This means that the default size of an integer is 16bits. The consequence of this is that we need to make sure that we only pass 16bit integers to cards.dll. DWORD values can stay as 32bits. The source-code handles this correctly.

The demonstration program uses the same 32bit function prototypes to access cards.dll regardless of which version of Windows you are using. The flat thunking is hidden behind a set of 32bit wrapper functions, so the test program does not need to know which version of Windows it is running under when it calls the cards.dll functions.

You may want to check out these two links for additional information on this flat thunking trick.

Original article by Matt Pietrek

Codeguru article on the same subject

22 May 2001

Posted in Tutorials

win32
Powered by Drupal


Cards.dll direct bitmap access | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
cards2.zip

Published on 22 Jan 2012

6.39 Kb

Download

Cards.dll direct bitmap access

Accessing the cards.dll bitmaps as resources

The first tutorial I wrote about cards.dll described how to use the exported card-drawing functions (cdtInit, cdtDraw etc). This tutorial will take a different approach to card drawing. Instead of calling the card-functions, I will show you how to directly access the bitmap resources stored inside cards.dll.

Small card pic

This method is about the same complexity as the original one I described, but there is much less work to do when it comes to accessing the bitmaps in the 16bit version of cards.dll.

How to load a Dynamic Link Library (32bits and 16bits)

There will be two different versions of cards.dll, depending on which operating system our program is run under. For Windows 95,98 and ME, there is a 16bit DLL. Under Windows NT, 2000 and XP, there is a 32bit DLL. Because we don't know in advance which DLL we will encounter, we need to dynamically load cards.dll.

Under the Windows NT series, this is very simple. All we need is a single call to LoadLibrary.

?
HINSTANCE hCardDll;
// Load the dynamic link library
hCardDll = LoadLibrary("cards.dll");
if(hCardDll == 0)
{
    // Error, cannot continue
    return FALSE;
}
// Load the bitmap resources
LoadCardBitmapsFromLibrary(hCardDll);
// Release the dynamic link library
FreeLibrary(hCardDll);

When it comes to the Windows 95 series, there is practically no change. The only difference is we use the undocumented LoadLibrary16 and FreeLibrary16 functions, which are exported from KERNEL32.DLL as ordinals 35 and 36, respectively. These are still 32bit functions, but they enable the caller to load a 16bit DLL. In order to use these functions from our application we need to create a .DEF file with the following content:

?
LIBRARY KERNEL32
EXPORTS
    LoadLibrary16@4                 @35
    FreeLibrary16@4                 @36

This is only half-way there. In order for our application to correctly link to these functions, we need to create an import library. The LIB.EXE tool that ships with the Platform SDK performs this task, and is located in the PlatformSDK\bin\link directory. Also, LIB.EXE that ships with Visual C++ (in the VC98\bin\link directory) will also do the job. Run LIB.EXE with the DEF file above to create our import library.

?
lib /machine:IX86 /def:cards16.def

Now that we have our import library, cards16.lib, we can link this with our application and then call LoadLibrary16 and FreeLibrary16 in the exact same way as before.

?
// Include a library-search record in the object file.
// This causes cards16.dll to be automatically linked in!
#pragma comment( lib, "cards16.lib" )
// Need to prototype the two undocumented functions, because
// they don't appear in windows.h
HINSTANCE WINAPI LoadLibrary16(PSTR);
void WINAPI FreeLibrary16(HINSTANCE);
?
HINSTANCE hCardDll;
// Load the dynamic link library
hCardDll = LoadLibrary16("cards.dll");
if(hCardDll == 0)
{
    // Error, cannot continue
    return FALSE;
}
// Load the bitmap resources
LoadCardBitmapsFromLibrary(hCardDll);
// Release the dynamic link library
FreeLibrary16(hCardDll);

Accessing the bitmap resources

We can load a bitmap from the resource section of any executable (including DLLs) by using the LoadBitmap API call. And by a stroke of luck, LoadBitmap can load bitmaps from 16bit DLLs as well as 32bit DLLs!!! Because we have a valid handle to a DLL (even a 16bit one), LoadBitmap just seems to treat the resource sections the same, which can only be good for us.

Using LoadBitmap is really easy. Just specify the DLL hInstance and a resource ID, and you can load any bitmap you want.

?
HBITMAP hBitmap = LoadBitmap(hCardDll, MAKEINTRESOURCE(dwIndex));

After a little experimentation, I found that the card bitmaps started with dwIndex=1. Also, the card bitmaps are stored in increasing order, suit by suit. So, first of all there are 13 clubs (Ace-King), then 13 diamonds, then hearts and spades. A simple for loop is enough to load each bitmap in turn.

?
HBITMAP hBitmap;
BITMAP  bmp;
int cardwidth, cardheight;
for(i = 0; i < 52; i++)
{
    // work out the correct card to retrieve
    DWORD dwIndex = (i % 4) * 13 + (i/4);
    // Load each bitmap in turn
    hBitmap = LoadBitmap(hCardDll, MAKEINTRESOURCE(dwIndex + 1));
    // find the bitmap dimensions
    GetObject(hBitmap, sizeof(bmp), &bmp);
    cardwidth  = bmp.bmWidth;
    cardheight = bmp.bmHeight;
    // * create a large memory DC now we know the bitmap size (first time only)
    // * copy bitmap into a large memory DC
    // free up the bitmap resource
    DeleteObject(hBitmap);
}

Take a close look at the first statement in the for-loop. This formula is necessary, because the arrangement of the card-bitmap resources is different than that used by the original cdtDraw functions. The resources are stored Ace-to-King, suit-by-suit, whilst cdtDraw enables access to the cards by a face-value grouping arrangement. That is, the aces come first, then all the twos, then the threes etc. Therefore a small bit of maths is required to convert from one arrangement to the other. The result will be that our card bitmaps are in the same order as we are used to.

Obviously its no good just loading the bitmaps and then freeing them again. We will create a memory device-context large enough to store all the bitmaps in, one after the other. This will make it convenient when we want to draw the card bitmaps.

?
HDC hMemDC = 0;
HBITMAP hMemBitmap = 0;
HDC hCardDC;
...
HDC hdc = GetDC(0);
// Create a memory DC for the big bitmap store
hMemDC      = CreateCompatibleDC(hdc);
hMemBitmap  = CreateCompatibleBitmap(hdc, cardwidth * NUMCARDBITMAPS, cardheight);
SelectObject(hMemDC, hMemBitmap);
// Create a temporary DC to store each card bitmap in
hCardDC     = CreateCompatibleDC(hdc);
ReleaseDC(0, hdc);

Now we can save off each bitmap as they are loaded.

?
HANDLE hOld;
...
hOld = SelectObject(hdcCard, hBitmap);
BitBlt(hMemDC, i * cardwidth, 0, cardwidth, cardheight, hdcCard, 0, 0, SRCCOPY);
SelectObject(hdcCard, hOld);

Fixing the card bitmaps

Red card pic When you have all the card bitmaps loaded, you will find that whilst all the "black" cards (clubs, spades) have a black single-line border, all of the "red" cards (hearts, diamonds) have a red single-line border. I prefer it when all cards have a black border (like the cdtDraw function draws them as), so it is necessary to modify each card as it is stored to give them a black border.

The code below draws a single black border around a specific card, once it is stored in the large memory DC.

?
int xpos = i * width;
MoveToEx(hMemDC, xpos+2,         0, 0);
LineTo  (hMemDC, xpos+width - 3, 0);
LineTo  (hMemDC, xpos+width - 1, 2);
LineTo  (hMemDC, xpos+width - 1, height - 3);   //vertical
LineTo  (hMemDC, xpos+width - 3, height - 1);
LineTo  (hMemDC, xpos+2,         height - 1);
LineTo  (hMemDC, xpos+0,         height - 3);
LineTo  (hMemDC, xpos+0,         2);
LineTo  (hMemDC, xpos+2,         0);

Drawing a card bitmap

After the loop has finished we can clean up, and then get ready to draw card bitmaps! We will define a function, CardBlt, which will draw a card bitmap into a specified DC, just like the original cdtDraw function. Because each card has slightly rounded edges, it would be a nice idea to draw each card with transparent corners. And because each card is the same shape we can forgoe the hassle of getting a transparent blitter working. Instead, I have opted to draw a card in strips - one main strip, and two shorter strips each side. The result is faster than doing a "proper" transparent blit, and is simpler too. Note that this technique assumes that the cards are all of a particular shape. Should the card bitmaps change shape radically in future versions of windows, then this method would fail to draw the cards properly.

?
void CardBlt(HDC hdc, int x, int y, int nCardNum)
{
    int sx = nCardNum * cardwidth;
    int sy = 0;
    int width  = cardwidth;
    int height = cardheight;
    //draw main center band
    BitBlt(hdc, x+2, y, width - 4, height, hMemDC, sx+2, sy+0, SRCCOPY);
    //draw the two bits to the left
    BitBlt(hdc, x,   y+2, 1, height - 4, hMemDC, sx+0, sy+2, SRCCOPY);
    BitBlt(hdc, x+1, y+1, 1, height - 2, hMemDC, sx+1, sy+1, SRCCOPY);
    //draw the two bits to the right
    BitBlt(hdc, x+width-2, y+1, 1, height - 2, hMemDC, sx+width-2, sy+1, SRCCOPY);
    BitBlt(hdc, x+width-1, y+2, 1, height - 4, hMemDC, sx+width-1, sy+2, SRCCOPY);
}

Conclusion

We're now ready to draw card bitmaps to our heart's content. The sample download implements a complete card bitmap loader, and demonstrates drawing the cards.

This tutorial took a different approach to accessing the card bitmaps in cards.dll. Instead of using a set of well defined functions to draw the bitmaps, we accessed them directly by looking into the resource section of cards.dll. The complexity of accessing the 16bit version of cards.dll has also reduced because the flat thunking was not necessary to extract the bitmap resources.

Look out for Part 3 of this card-drawing series, which I will be releasing shortly. In this tutorial I will be presenting the card-game engine I developed whilst programming Shed (the card-game which you can find on this site). This C++ library provides powerful card handling and drawing support.

1 Oct 2001

Posted in Tutorials

win32
Powered by Drupal


CardLib | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
CardLib.zip

Published on 22 Jan 2012

68.81 Kb

Download

CardLib

Introducing CardLib, a card-game programming library

(How to use cards.dll, Part 3)

Updated 14th Jul 2005

  • Fixed bug in Cardlib's CardRgnMouse.cpp which caused cardgames to hang when a stack was clicked (only occurred when compiled with VC7.0+)

Introduction

The previous two tutorials in this series concentrated on accessing the playing-card bitmap images from cards.dll, and demonstrated how to simply draw a playing-card into a window.

This tutorial presents CardLib, a fully featured card-game library, which provides a framework to manipulate cards and collections of cards.

Cards can be dragged around the screen from one stack to another. Card-stacks are fully customizable. A function-callback system allows the programmer to register callback functions with different events. This system provides complete control over playing card manipulation.

{short description of image}

I decided to write this library using C++. No external libraries such as MFC or ATL are required to use the library. However, there is nothing to stop you from using these frameworks alongside CardLib. CardLib requires the use of a C++ compiler. The project currently only supports Visual C++ 6.0, but other 32bit Windows compilers should also work because there are no Visual C++ extensions used by the project.

I chose C++, because it is the language which lends itself best to this type of library. Originally I had wanted to use C instead of C++, but I encountered many problems designing the library. This was because an inherently object-oriented concept was being designed, and I finally decided that C++ was the way forward. This, in addition to the type-safety C++ provides, has resulted in an easier to use and better designed game library. (Well, I think so, anyway!).

For the purely C folk reading this, please don't be put off. The library is really easy to use, and I havn't used any advanced C++ features such as templates or polymorphism. In fact, most of the functional game code that you write will use very few C++ features. If you can write C programs, you can use this library.

Create a playing table

The first thing you must do in a CardLib program is to link with CardLib.lib, and #include "CardLib.h". Once you have done this, you are free to start using the library. Next, you must create a CardWindow which forms the basis of all subsequent coding. Therefore, you need to declare a class object of type CardWindow somewhere in your program.

?
CardWindow cardwnd;

If you are writing a bare-bones Win32 application, then this declaration could just be a global variable. For an MFC application, you would define this variable as a class member in a CView class.

By itself, this declaration doesn't really do much. In order to actually create a physical card window, you need to call the CardWindow::Create member function.

?
BOOL CardWindow::Create(HWND hwndParent, DWORD dwExStyle, DWORD dwStyle,
                            int x, int y, int width, int height);

With this function, you specify the parent window of the card-window, the window styles, and the card window location. A typical usage of this function might look like this:

?
cardwnd.Create(hwnd, WS_EX_CLIENTEDGE, WS_CHILD|WS_VISIBLE, 0, 0, 100, 100);

In order to manipulate the physical card window, you simply pass the CardWindow object to any Win32 API call you desire. The CardWindow class automatically converts to a HWND window handle whenever you need one. So, to resize the card window in a WM_SIZE handler, for example, you would write something like this:

?
case WM_SIZE:
    nWidth  = (LOWORD)lParam;
    nHeight = (HIWORD)lParam;

MoveWindow(cardwnd, 0, 0, nWidth, nHeight, TRUE); break;

At this point you don't need to do anything else. The card window is currently empty though, apart from the default dark-green background. It would be nice if we could create some playing cards somehow, and drag them around.

Create a Playing Card

The basic element of any card-game is a playing card. CardLib provides a built-in C++ class called Card. A card has a number of attributes, listed below:

  • Face-value, or Rank (Ace-King)
  • Suit (Clubs, Diamonds, Hearts and Spades)
  • Face-direction (facing Up or Down)

It is simple to create a playing card - just create a Card variable.

?
Card card(Ten, Diamonds);
Card card(48);

The example above shows two methods of intializing a playing card. The first method is obvious - specify the suit followed by the rank. The second method needs explaining though. The single-argument constructor for a Card requires an integer value in the range 0-51, inclusive. This value uniquely indentifies a playing card. You can calculate this value using the following formula, or use the MAKE_CARD function, whichever you prefer.

?
int MAKE_CARD(int Value, int Suit);
... or ...
int cardval = (Value - 1) * 4 + Suit;

Whilst playing cards are essential to a card game, a Card object provides no method to display itself - a Card is just a basic C++ class. To actually display and interact with a Card object, you must first place the card on a card-stack.

Create a Card Stack

CardLib implements a stack-orientated card system. Rather than creating individual playing cards and positioning them manually on the screen, CardLib gives you the power to manipulate and interact with collections of cards instead. A CardStack is a C++ data structure which provides a "stack" metaphore to one or more playing cards. Cards are accessed from the top of the stack, and cards can be pushed and popped from a stack in whatever way you want.

Note that a CardStack still doesn't provide any way to draw itself on the screen, or any way for the user to interact with it. The sole purpose of a CardStack object is to collect one or more cards together in an ordered manner. The following example shows how to create and manipulate a CardStack.

?
Card card(8, Spades);
CardStack cardstack;
/* Add, then remove a card */
cardstack.Push(card);
cardstack.Pop();

Once you have created a CardStack and populated it with cards, it is finally time to display it on the screen.

Create a Card Region

A CardRegion object is a place-holder for a CardStack. This is basically a region in a CardWindow which can hold and display a CardStack. Every CardRegion object contains one CardStack. The contents of the CardStack dictate how the CardRegion draws itself. You create a CardRegion using the following CardWindow member function:

?
CardRegion* CardWindow::CreateRegion (int id, bool fVisible,
                       int x, int y, int xoffset, int yoffset);

With this function, you specify a unique ID, whether or not the stack is initally visible, the coordinates of the stack, and the direction that cards take when collected on the stack. A typical usage of this function might look like this:

?
#define MY_CARD_STACK 1
CardRegion *pRegion1;
...
pRegion1 = cardwnd.CreateRegion(MY_CARD_STACK, true, 10, 10, 20, 3);

The CardWindow object performs all necessary allocation for you, and performs all the management required to draw the card-region and interact with it. You don't need to store the CardRegion pointer that is returned - you can retrieve this at any time by using the CardWindow::CardRegionFromId(int Id) member function. However, you can perform alot of customization on the CardRegion itself, so you will probably find yourself storing this pointer for later use.

Once a CardRegion has been created, you can add a CardStack to it, to be displayed and interacted with. Use the CardRegion::SetCardStack method to accomplish this. The following example shows how to create a stack of cards and add it to a CardRegion for display.

?
CardStack cardstack;
Card card1(Jack, Clubs);
Card card2(Ace, Hearts);
cardstack.Push(card1);
cardstack.Push(card2);
pRegion1->SetCardStack(cardstack);

Note that the CardRegion does not take "ownership" of the CardStack passed to it - a CardRegion keeps its own internal CardStack. Therefore if you modified the CardStack after passing it to CardRegion::SetCardStack, this would have no effect on the region's internal CardStack.

At this stage we have created a fully functional stack of cards which can be dragged around the screen. Creating additional stacks is left as an exercise to the reader. The only thing left to do is to customize the appearance and behaviour of the CardRegion.

There are quite a few CardRegion member functions to choose from. The CardLib Programmer's Reference describes each function available to you.

Create a Button

The CardWindow object supports buttons as well as card-stacks. These buttons are not "real" button controls, rather they are built into the card window, and share the same colour scheme. It is not necessary to create any buttons in your card game, but sometimes they can be useful if you want to give the user some kind of control to click on, say to shuffle a card-stack, or to pickup some cards from one stack and place them in another.

You create a CardButton object using the following CardWindow member function:

?
CardButton* CardWindow::CreateButton(int id, TCHAR *szText, UINT uStyle,
         bool fVisible, int x, int y, int width, int height);

Again, it is not necessary to store the CardButton pointer that is returned, but it will be useful if you want to customize the button, for example setting if it is centered or right-aligned.

Provide Drag and Drop Behaviour

Card stack behaviour is the key to implementing a card-game. By default, a card-stack will let you drag and drop any number of cards from and to it. It is unlikely that you will want to allow this type of behaviour, so specifying which cards can be dragged to and from stacks gives you greater control over stack manipulation.

There are only two functions which control stack behaviour. These are:

?
bool CardRegion::SetDragRule (UINT uDragType, pCanDragProc proc=0)

Card dragging is controlled using the SetDragRule member function. There are four different types of card dragging:

  • CS_DRAG_NONE prevents any cards from being dragging from a stack.
  • CS_DRAG_TOP restricts dragging to the top card only.
  • CS_DRAG_ALL allows any number of cards to be dragged (the default).
  • CS_DRAG_CALLBACK allows you to specify a custom callback function which is executed whenever the user tries to drag cards from a stack. The callback function returns a boolean value to indicate whether or not to allow the drag action to contine. More information on callbacks can be found in the next section.
?
bool CardRegion::SetDropRule (UINT uDropType, pCanDropProc proc=0)

Card dropping is controlled using the SetDropRule member function. There are three different types of drop behaviour:

  • CS_DROP_NONE prevents any cards from being dropped.
  • CS_DROP_ANY allows any number of cards to be dropped (the default).
  • CS_DROP_CALLBACK allows you to specify a custom callback function which is executated whenever the user tries to drop cards on a stack. The callback function returns a boolean value to indicate whether or not to allow the drop to take place. More information on callbacks can be found in the next section.

SetDragRule and SetDropRule both accept an optional second parameter - a pointer to a callback function. This parameter must only be specified when using the CS_xxxx_CALLBACK rule.

Create an Event Callback

Event callbacks provide complete control over stack manipulation. Here is a list of the different types of callback that a StackObject supports.

Type | Return type | Description
CanDragProc | bool | Controls how many cards can be removed from a stack.
CanDropProc | bool | Controls the type of card that can be dropped on a stack.
ClickProc | void | Single-click notification of one of the cards in a stack.
DblClickProc | void | Double-click notification of one of the cards in a stack.
AddProc | void | Sent after cards have been dropped (added) on a stack
RemoveProc | void | Sent after cards have been dragged (removed) from a stack.

Drag Callback

A drag callback must use the following prototype.

?
bool CARDLIBPROC CanDragProc(CardRegion &cardrgn, int iNumDragging);

The CardRegion parameter identifies which card-stack is being manipulated.

The iNumDragging variable specifies how many cards (from the top) are being removed.

The callback must return true to allow the drag, or false to prevent any cards from being removed.

Drop Callback

A drop callback must use the following prototype.

?
bool CARDLIBPROC CanDropProc(CardRegion &cardrgn, CardStack &cardstack);

The CardRegion parameter identifies which card-stack is having cards dropped on it.

The CardStack object contains the list of cards that are being dropped.

The callback must return true to allow the drop, or false to prevent any cards from being dropped.

Mouse Click Callback

A mouse-click callback is set using the CardRegion::SetClickProc member function.

?
void CardRegion::SetClickProc (pClickProc proc);

The function you pass to SetClickProc must use the following prototype.

?
void CARDLIBPROC ClickProc(CardRegion &cardrgn, int iNumClicked);

The CardRegion parameter identifies which card-stack has been clicked on.

The iNumClicked variable specifies which card (from the top) has been clicked on. This variable will range from 0 (the top card) to n-1 (the number of cards in the stack).

The callback does not return a value.

Mouse Double-Click Callback

A mouse double-click callback is set using the CardRegion::SetDblClickProc member function.

?
void CardRegion::SetClickProc (pClickProc proc);

The callback function uses the exact same function prototype as the single-click callback.

?
void CARDLIBPROC ClickProc(CardRegion &cardrgn, int iNumClicked);

The CardRegion parameter identifies which card-stack has been double-clicked on.

The iNumClicked variable specifies which card (from the top) has been double-clicked on.

The callback does not return a value.

Add Callback

The card-add callback is set using the CardRegion::SetAddCardProc member function.

?
void CardRegion::SetAddCardProc(pAddProc proc);

The callback is called after any card(s) have been dropped on a card-stack, and must use the following prototype.

?
void CARDLIBPROC AddProc(CardRegion &cardrgn, CardStack &cardstack);

The CardRegion parameter identifies which card-stack has been clicked on.

The CardStack parameter specifies the list of cards which have been added.

The callback does not return a value.

Remove Callback

The card-remove callback is set using the CardRegion::SetRemoveCardProc member function.

?
void CardRegion::SetRemoveCardProc(pRemoveProc proc);

The callback is called after any card(s) have been dragged from a card-stack (and subsequently dropped on another stack). The remove-callback must use the following prototype.

?
void CARDLIBPROC RemoveProc(CardRegion &cardrgn, int iNumRemoved);

The CardRegion parameter identifies which card-stack has been clicked on.

The iNumRemoved variable specifies how many cards were removed.

The callback does not return a value.

Conclusion

Once again, this is work in progress. It's not that there are any problems with the library, but this is my first real stab at a framework of this sort, so there are undoubtably some areas that could be improved, from a design perspective.

I am confident that the library works well. It took me just over an hour to write a complete Solitiare game using the library, so I think this speaks for itself.

Well, if you have any comments or ideas for improvement, then I'd like to hear them..

James.

4 Dec 2001

Posted in Tutorials

win32
Powered by Drupal


CardLib Reference | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

CardLib Reference

CardLib Programmer's Quick-Reference

CardLib User Guide

The following document describes the various C++ classes which make up the CardLib game library.

class Card

Function Prototype | Description
int Suit() | Returns the suit of the card [0..3]
int LoVal() | Returns the value of the card, Aces Low, [1..13]
int HiVal() | Returns the value of the card, Aces High, [2..14]
int Idx() | Returns the unique card index, [0..51]
bool FaceUp() | Returns if card is face-up
bool FaceDown() | Returns if card is face-down
void SetFaceUp(bool) | Sets the face direction
bool IsBlack() | Returns if card is Clubs or Spades
bool IsRed() | Returns if card is Diamonds or Hearts

class CardStack

Function Prototype | Description
void NewDeck() | Allocates a new 52-card deck
int NumCards() | Returns number of cards in stack
void Shuffle() | Shuffles the cards in the stack
void Clear() | Removes all cards from the stack
void Reverse() | Reverses the order of cards
void Push(Card card) | Pushes the specified card
void Push(CardStack &cardstack) | Pushes the specified CardStack
Card Pop() | Pops the top-most card
CardStack Pop(int items) | Pops the top-most n-cards
Card Top() | Peeks the top-most card, without removing it
CardStack Top(int items) | Peeks the top-most n-cards, without removing them
Card RemoveCard(size_t index) | Removes the card at specified index
void InsertCard(size_t index, Card card) | Inserts a card at specified index

class CardButton

Function Prototype | Description
void SetStyle(UINT uStyle) | Sets the button style (push-button, label etc).
UINT GetStyle() | Returns the button style.
void SetText(TCHAR *fmt, ...) | Set the button text.
void SetFont(HFONT hFont) | Set the button type-face.
void SetPlacement( ... ) | * Aligns the button within it's parent CardWindow.
void SetForeColor(COLORREF col) | Set the button text colour.
void SetBackColor(COLORREF col) | Set the button background colour.
void Move(int x, int y, int w, int h) | Re-position the button.
void Show(bool fShow) | Show / Hide the button.
void Redraw() | Force the button to repaint itself.
void Id() | Return the button ID.

* See CardRegion::SetPlacement for more details.

{short description of image}

class CardRegion

Function Prototype | Description
void SetBackColor(COLORREF cr) | Set a new colour-scheme for the cardgame.
void SetCardStack(const CardStack &cs) | Assign a new CardStack.
const CardStack & GetCardStack() | Retrieve the current CardStack.
bool SetThreedCount(int count) | Control how high a card-stack can appear by grouping cards together. (Useful for decks with large numbers of cards).
void SetOffsets(int x, int y) | Control the direction that stacked cards take.
void SetPos(int x, int y) | Position the card region.
void Show(bool fShow) | Show / Hide the card region.
bool IsVisible() | Is the card region visible?
void SetEmptyImage(UINT uImage) | Set the image used to represent an empty card stack. Use CS_EI_NONE or CS_EI_SUNK.
void SetBackCardIdx(UINT uBackIdx) | Set the card bitmap used for the card-backs.
void SetPlacement( ... ) | Align the card region within its parent CardWindow.
void Redraw() | Force the card region to repaint itself.
void SetFaceDirection( ... ) | Control how cards are placed on the stack (face-down, face-up, or a combination of the two).
UINT GetFaceDirection( ... ) | Return the current face-direction rule.
void Flash(int count, int timeout) | Make the stack flash a number of times.
void StopFlash() | Stop the stack flashing.
int Id() | Return the ID.
bool PlayCard( ... ) | Move the specified number of cards with index [0..51] to the specified CardRegion.
bool MoveCard( ... ) | Move the specified number of cards from the top of the stack.
CardStack wrappers (for the internal stack). |
int NumCards() | Returns the number of cards in the internal CardStack.
int NewDeck() | Allocates a new 52-card deck.
int Shuffle() | Shuffles the cards in the stack.
int Clear() | Removes all cards from the stack.
Event Callbacks (see user guide for details). |
SetDragRule | Set the dragging rule for the CardStack.
SetDropRule | Set the drop rule.
SetClickProc | Mouse-button click event.
SetDblClickProc | Mouse-button double-click event.
SetAddCardProc | Post-add event callback.
SetRemoveCardProc | Post-remove event callback.

The following list describes a few of the functions in the above table which need more careful explanation.

CardRegion::SetPlacement

?
void SetPlacement(UINT xJustify, UINT yJustify,
                      int xAdjust, int yAdjust)

Controls the alignment of the CardRegion within it's CardWindow.

xJustify specifies the horizontal alignment. Possible values are:

CS_XJUST_NONE | Default alignment (left-aligned).
CS_XJUST_CENTER | Aligned to the center of the CardWindow.
CS_XJUST_RIGHT | Aligned to the right side of the CardWindow.

yJustify specifies the vertical alignment. Possible values are:

CS_YJUST_NONE | Default alignment (aligned to the top of the window).
CS_YJUST_CENTER | Aligned to the middle of the CardWindow.
CS_YJUST_RIGHT | Aligned to the bottom of the CardWindow.

xAdjust specifies a horizontal offset (or border) to use. This offset is applied when the card-region is aligned to the center or right of the window. A positive value offsets the card-region to the right, a negative value offsets to the left.

yAdjust specifies a vertical offset (or border) to use. This offset is applied when the card-region is aligned to the middle or bottom of the window. A positive value offsets the card-region downwards, a negative value offsets the card-region upwards.

CardRegion::SetFaceDirection

?
void SetFaceDirection(UINT uDirType, int nOption)

Controls how cards are placed onto the card-stack. Cards can be either face-up, face-down, or a combination of the two.

uDirType specifies what the face-direction is. Possible values are:

CS_FACE_UP | All cards will be face-up when added to the stack.
CS_FACE_DOWN | Cards will be face-down.
CS_FACE_DOWNUP | The bottom most nOption cards will be face-down, all other cards on top of these will be face-up.
CS_FACE_UPDOWN | The bottom most nOption cards will be face-up, all other cards on top of these will be face-down.
CS_FACE_ANY | Cards can be either face-up or face-down.

nOption is used in conjunction with CS_FACE_DOWNUP and CS_FACE_UPDOWN. This value controls the face-direction of the bottom-most nOption cards.

For example, assume that uDirType is CS_FACE_DOWNUP and nOption is 3. This means that the bottom 3 cards will always be face-down, and any cards above these 3 will always be face-up.

CardRegion::GetFaceDirection

?
UINT GetFaceDirection(int *pnOption)

Returns the current face-direction rules.

This function returns the direction-type variable, and stores the option in the address specified by pnOption.

CardRegion::Flash

?
void Flash(int count, int timeout)

Causes the card-region to flash on and off. This is achieved by hiding and then showing the card-region multiple times.

count specifies how many times the card-region will be hidden and shown again.

timeout specifies the duration of each stage of animation, in milliseconds.

CardRegion::StopFlash

?
void StopFlash()

Stops immediately any stack-flashing that might be active.

CardRegion::PlayCard

?
bool PlayCard(CardRegion *pDest, int val, int num)

Moves num cards of face-value val to the specified card-region.

val specifies the face-value of the card, using "Aces High" numbering [2..14].

num identifies the number of these cards to move.

PlayCard returns true on success, false on failure.

CardRegion::MoveCard

?
bool MoveCard(CardRegion *pDest, int nNumCards, bool fAnimate)

Moves the specified number of cards from the top of the current stack, to the specified destination stack.

nNumCards specifies how many cards are to be moved.

fAnimate is a boolean value, which specifies if the cards are to be animated when they are moved, or (when fAnimate is false) moved without any animation.

MoveCard returns true on success, false on failure.

{short description of image}

class CardWindow

Function Name | Description
Create | Create a physical card window.
CreateButton | Create a button.
CreateRegion | Create a card-region.
CardButtonFromId | Return the card-button with specified ID.
CardRegionFromId | Return the card-region with specified ID.
DeleteButton | Deletes the specified button.
DeleteRegion | Deletes the specifies card-region.
DeleteAll | Deletes ALL regions, buttons and drop-zones
EmptyStacks | Empty all card-stacks in the card-window.
Redraw | Force the card-window to repaint itself.
Update | Update the position of all card-stacks and buttons.
DistributeStacks | Position a range of card-stacks in an aligned manner.
SetResizeProc | Specify a callback function for window resizes.
GetWidth | Return the width of the card-window.
GetHeight | Return the height of the card-window.
SetBackColor | Set the current background colour.
GetBackColor | Return the current background colour.
SetBackCardIdx | Specify which bitmap is to be used as the card-backs.
SetBackImage | Set the bitmap to be used as window background.
RegisterDropZone | Creates a new DropZone area.
DeleteDropZone | Deletes the specified DropZone.

The CardWindow functions are not as obvious as the previous classes, so following section describes each CardWindow member function separately.

CardWindow::Create

?
BOOL Create(HWND hwndParent, DWORD dwExStyle, DWORD dwStyle,
                  int x, int y, int width, int height)

Creates a physical window which will form the basis for a card-game.

CardWindow::CreateButton

?
CardButton *CreateButton(int id, TCHAR *szText, UINT uStyle, bool fVisible,
                             int x, int y, int width, int height)

Create a button within the window.

id specifies a unique integer ID for the button.

szText is the address of a null-terminated string, which contains the button text.

uStyle specifies the button styles. The following styles are supported.

CB_STATIC | Creates a static text label (default)
CB_PUSHBUTTON | Creates a normal push-button
CB_ALIGN_CENTER | Centers the button text (default)
CB_ALIGN_LEFT | Left-aligns the button text
CB_ALIGN_RIGHT | Right-aligns the button text

fVisible specifies whether or not the button is visible.

x, y specifies the position of the button.

width, height specifies the dimensions of the button.

CardWindow::CreateRegion

?
CardRegion *CreateRegion(int id, bool fVisible,
                             int x, int y, int xoffset, int yoffset)

Create a card-stack region within the window.

id specifies a unique integer ID for the region.

fVisible specifies whether or not the region is visible.

x, y specifies the position of the region.

xoffset, yoffset specifies the direction that cards take when placed on the stack.

CardWindow::CardButtonFromId

?
CardButton *CardButtonFromId(int id)

Returns a pointer to the CardButton object with the specified ID.

CardWindow::CardRegionFromId

?
CardRegion *CardRegionFromId(int id)

Returns a pointer to the CardRegion object with the specified ID.

CardWindow::DeleteButton

?
bool DeleteButton(CardButton *pButton)

Destroy the specified button. Do not use the delete operator to delete a button - use this function instead. Note that the CardWindow will automatically delete all buttons when it is deleted itself.

CardWindow::DeleteRegion

?
bool DeleteRegion(CardRegion *pRegion)

Destroy the specified region. Do not use the delete operator to delete a region - use this function instead. Note that the CardWindow will automatically delete all region when it is deleted itself.

CardWindow::DeleteAll

?
bool DeleteAll()

Destroys every CardRegion, CardButton and drop-zone. All pointers to these objects become invalid.

CardWindow::EmptyStacks

?
void EmptyStacks()

Empty ALL card-stacks currently in the card-window.

CardWindow::Redraw

?
void Redraw()

Force the card-window to repaint itself.

CardWindow::Update

?
void Update()

Force the card-window to reposition all stacks and buttons.

CardWindow::DistributeStacks

?
bool DistributeStacks(int nIdFrom, int nNumStacks,
                           UINT xJustify, int xSpacing, int nStartX)

Physically distribute the specified card stacks. The stack with the specified id (nIdFrom) is used as the starting point., and the nNumStacks created after this stack will have their positions modified.

xJustify specifies the type of alignment to use:

CS_XJUST_NONE | No alignment (left-aligned, default)
CS_XJUST_CENTER | Center alignment
CS_XJUST_RIGHT | Right-aligned

xSpacing specifies how far apart each stack is to be placed.

nStartX specifies the starting x-coordinate to place the series of card-stacks.

CardWindow::SetResizeProc

?
void SetResizeProc(pResizeWndProc proc)

Specify a callback function, which is executed whenever the CardWindow's size is altered. The callback function must have the following prototype:

?
void CARDLIBPROC ResizeWndProc(int width, int height)

CardWindow::GetWidth

?
int GetWidth()

Returns the width (in pixels) of the card-window.

CardWindow::GetHeight

?
int GetHeight()

Returns the height (in pixels) of the card-window.

CardWindow::SetBackColor

?
void SetBackColor(COLORREF cr)

Set the current background colour.

CardWindow::GetBackColor

?
COLORREF GetBackColor()

Returns the current background colour.

CardWindow::SetBackCardIdx

?
void SetBackCardIdx(UINT uBackIdx)

Specify which card-bitmap is to be used as the card-back.

uBackIdx must be in the range [53..68] inclusive. Use one of the ecbxxx constants defined in CardLib.h

CardWindow::SetBackImage

?
void SetBackImage(HBITMAP hBitmap)

Specifies a bitmap to be used as the card-window background. The CardWindow does not assume ownership of the bitmap.

hBitmap must be a valid handle to a bitmap object, or NULL to remove the background bitmap.

CardWindow::RegisterDropZone

?
bool RegisterDropZone(int id, RECT *rect, pDropZoneProc proc)

Creates a DropZone within the card-window.

id specifies a unique ID for the DropZone. This id must NEVER be -1.

rect is the address of a RECT structure, which specifies the coordinates of the DropZone.

proc is the address of a callback function, which is executed whenever a card is dropped inside the rectangular area.

A DropZone is a special area within a CardWindow which cards can be dropped on. Even if a card-stack is within the area, the cards will not be dropped on it.

The specified function callback is called whenever one or more cards are dropped within the zone. This function must have the following prototype:

?
int CARDLIBPROC DropZoneProc(int dzid, const CardStack &cards)

The callback function returns the ID of the stack to drop the specified cards onto. Using this mechanism provides complete control over which stack cards are dropped.

The function must return a valid ID of a CardRegion to send the cards to, or use a value of CS_DROPZONE_NODROP to prevent the cards from being dropped.

CardWindow::DeleteDropZone

?
bool DeleteDropZone(int id)

Deletes the DropZone with the specified ID.

4 Dec 2001

Posted in Tutorials

win32
Powered by Drupal


Splitter Windows | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
splitter.zip

Published on 22 Jan 2012

3.58 Kb

Download
EasySplitter_20.zip

Published on 22 Jan 2012

33.94 Kb

Splitter Windows

An example of a splitter-bar used to separate two windows

Updated 5 Jan 2006

I've added a new sourcecode download from L.D. Blake who kindly sent me a great little splitter library called "Easy Split". From the readme:

"EasySplitter is a custom control, in library format, that can be used with Pelles C to implement a very simple splitter window. With EasySplitter you create a splitter bar as a windows control that you can size, move and stylize like any other windows control. All mouse capture and window drag operations are handled internally by the control itself."

Introduction

There is no such thing as a "splitter window". Sorry to disappoint anyone who wants to implement one, but I can't say it any simpler than that. Neither does there exist any method to split a window in half or quarters. What you are seeing when you see a so-called "splitter window" is a visual illusion.

What people most likely mean when they say "splitter windows", is the horizontal or vertical bar which separates two windows. This bar can be dragged back and forth using the mouse to resize the two windows which it separates.

There is no trickery used to perform this behaviour. This resizing or splitter bar is just a standard window, but the windows it divides and the bar itself are arranged in such a way to give the required effect.

There are two main ways of implementing a divider bar between two windows. They differ only in the organisation and relationship of the windows involved.

Window A
Frame Window
Window B
Window A
Divider Bar
Window B
Method 1 Method 2
  1. The first method is to host the windows you want to divide in a "frame" window. This frame window is just a standard window, but it handles all of the divider mouse messages. This frame window contains all the windows that need to be divided from each other. This frame window doesn't have to be the main window of an application, although it could be.
  2. The second method uses a separate window which is positioned and sized to act like a divider-bar in-between the two windows that you want to divide.

Both cases take advantage of the way that standard windows are drawn. Because the two windows will be positioned close to each other, their 3d borders give the illusion that there is a 3d bar in-between the two windows. In reality, it is just a standard flat grey window with no fancy graphics.

Mouse cursor display

To make it obvious to the user that our splitter bar actually does something useful, then it is necessary to display some kind of sizing mouse cursor when the mouse moves over the "bar".

A cursor similar to the two above is probably the right kind of thing to display. There are two ways to display a custom cursor in a window.

  1. The first is to process the WM_SETCURSOR message. This is the best way if you want to support different types of cursor within the same window. By processing this message, you can use the SetCursor API call to set the cursor. This is the most flexible way to modify a window's mouse cursor.
  2. The second way, and the easiest, is to specify what the mouse cursor should be when you register the window class associated with the window you want to create. Because we are creating a new type of window to create the splitter effect, we can set the hCursor member of the WNDCLASSEX structure when we register the splitter class.

This is the code that we use to register the splitter class.

?
WNDCLASSEX wc;
 ...
 wc.cbSize        = sizeof(wndclass);
 wc.style         = 0;
 wc.lpfnWndProc   = SplitterProc;
 wc.cbClsExtra    = 0;
 wc.cbWndExtra    = 0;
 wc.hInstance     = hInstance;
 wc.hIcon         = NULL;
 wc.hCursor       = LoadCursor (hInstance, MAKEINTRESOURCE(IDC_HORZSPLITTER));
 wc.hbrBackground = (HBRUSH)(COLOR_3DFACE+1);
 wc.lpszMenuName  = NULL;
 wc.lpszClassName = "Splitter";
 wc.hIconSm       = NULL;
 RegisterClassEx(&wc);

Handling Mouse Messages

To make a useable splitter bar, we need to handle the three standard mouse messages. Typical code that we would want to execute when the mouse button is pressed over our splitter window is below. Note that this is not executable code - it is presented to give an idea of the steps necessary to implement the dragging around of a splitter bar.

Mouse button down (WM_LBUTTONDOWN)

?
splitpos  = GetCursorPos();
fDragMode = TRUE;      /* set dragging to active */
SetCapture(hwnd);      /* capture the mouse so we receive all messages */
hdc = GetDC(hwnd);     /* Draw the checkered resizing bar */
DrawXorBar(hdc, splitpos);
ReleaseDC(hwnd);
old_splitpos = splitpos;

Mouse move (WM_MOUSEMOVE)

?
if(fDragMode == FALSE) return;
splitpos  = GetCursorPos();
hdc = GetDC(hwnd);

DrawXorBar(hdc, old_splitpos);  /* Erase where the bar WAS */
DrawXorBar(hdc, splitpos);      /* Draw it where it IS now */
ReleaseDC(hwnd);
old_splitpos = splitpos;

Mouse button up (WM_LBUTTONUP)

?
if(fDragMode == FALSE) return;
fDragMode = FALSE;     /* set dragging to unactive */
ReleaseCapture();      /* release the mouse capture: we don't need it */
hdc = GetDC(hwnd);     /* Draw the bar again to erase it */
DrawXorBar(hdc, old_splitpos);
ReleaseDC(hwnd);

Although the code above is just a sample of what needs to be achieved, in reality the code is not much more complex. Probably the most complicated thing to do is to draw some kind of checkered bar to provide feed-back when the splitter bar is dragged around.

Drawing the feedback bar

The feedback bar is just a simple rectangle, but drawn using a checkered pattern. Because we draw the rectangle using the PATINVERT style, we can easly restore the background to its original state by simply drawing the same rectangle back to the original position. So, calling DrawXorBar twice in succession will draw the checkered bar, but then immediately erase it again, restoring the background.

?
void DrawXorBar(HDC hdc, int x1, int y1, int width, int height)
{
  static WORD _dotPatternBmp[8] = { 0x00aa, 0x0055, 0x00aa, 0x0055,
                                    0x00aa, 0x0055, 0x00aa, 0x0055};
  HBITMAP hbm;
  HBRUSH hbr, hbrushOld;
  /* create a monochrome checkered pattern */
  hbm = CreateBitmap(8, 8, 1, 1, _dotPatternBmp);
  hbr = CreatePatternBrush(hbm);
  SetBrushOrgEx(hdc, x1, y1, 0);
  hbrushOld = (HBRUSH)SelectObject(hdc, hbr);
  /* draw the checkered rectangle to the screen */
  PatBlt(hdc, x1, y1, width, height, PATINVERT);

  SelectObject(hdc, hbrushOld);
  DeleteObject(hbr);
  DeleteObject(hbm);
}

Conclusion

I have presented the basic steps necessary to create a divider bar, or splitter window as it is more commonly refered to. What I haven't described is how to create a generic frame-work which magically does all of this work for you. If you want a frame-work to do this, then go and use MFC in your projects. Otherwise, it is up to you, the programmer, to decide how to arrange and manage your windows.

This sample project below implements a simple splitter bar to get you started.

22 May 2001

Posted in Tutorials

win32
Powered by Drupal


Docking Toolbars - Part 1 | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
dock1.zip

Published on 22 Jan 2012

8.31 Kb

Download

Docking Toolbars - Part 1

How to implement floating toolbars

Introduction

This tutorial will show you how to create docking toolbars, using pure Win32 techniques. I'm going to split this topic in two separate pages. The first tutorial will cover the "floating" aspect of toolbars - i.e. how to get tool windows to stay floating on top of all other windows, how to get the window activation working correctly etc. The second tutorial will show you how to get these floating toolbars to "dock" to the side of a window, and discuss various methods of window management.

How to create a floating toolbar

A floating toolbar is just a standard window with the WS_POPUP style set. When a popup window is created with an owner window, the popup is positioned so that it always stays on top of that owner window. This is how you might create and display a floating window:

?
// create a tool window
HWND hwnd = CreateWindowEx(
    WS_EX_TOOLWINDOW,
    "ToolWindowClass", "ToolWindow",
    WS_POPUP | WS_SYSMENU | WS_THICKFRAME | WS_CAPTION,
    200, 200, 400, 64,
    hwndParent, NULL, GetModuleHandle(0), NULL
    );

The WS_EX_TOOLWINDOW style doesn't do anything special, other than to make a window with a smaller titlebar. It doesn't make the window magically float - this is achieved automatically by specifying WS_POPUP and an owner window (hwndParent).

{short description of image}

A window can also be positioned to stay at the top of the Z-order with a call to SetWindowPos:

?
// position the window to appear above all others
SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);

Prevent window deactivation

The image shown above shows the main window's titlebar as inactive. This is entirely normal, because only one window can be active at any one time in Windows. However, it has become normal practise for tool windows and main windows to appear active at the same time. I think it looks more natural this way, so we need to devise a strategy to keep windows appearing active, even if they aren't.

The key to the problem is the WM_NCACTIVATE message. This message is sent when a window's non-client area needs to be activated or deactivated. As with all window messages, WM_NCACTIVATE is sent with two parameters - wParam and lParam:

  • When a window receives WM_NCACTIVATE with wParam=TRUE, this indicates that the non-client area (the titlebar and border) needs to become active.
  • When wParam=FALSE, this indicates that the non-client area needs to become inactive.
  • MSDN states that lParam will always be 0. However, I have observed that lParam indicates the window handle of the window being deactivated. This appears to be true under win95,98 and NT,2000.
  • A window can return TRUE to prevent the non-client area from being deactivated.

Now, when this message is passed to DefWindowProc, two things happen. The first is to redraw the titlebar as either active or inactive, depending on the value of wParam. The second is to set an internal flag for the window which basically remembers if the window was painted as active or inactive. This enables subsequent WM_NCPAINT updates to correctly paint the titlebar the way it was requested. It is therefore advisable to always pass WM_NCACTIVATE to DefWindowProc for proper processing.

If you havn't realised already, this WM_NCACTIVATE messages provides us with a way to make all our application tool windows look active, even if they aren't. Try adding the following code to any window which you want to keep active (this includes any tool windows and the main frame window):

?
case WM_NCACTIVATE:
    return DefWindowProc(hwnd, msg, TRUE, lParam);

By overriding any requests to make the non-client area appear inactive, we can create the effect we want.

{short description of image}

Note that MDI child windows also use this same technique to keep their titlebars active. The only difference is that MDI windows have the WS_CHILD style set, instead of WS_POPUP.

Proper window activation

The method shown above is all well and good, but there is a problem. The main window and tool windows will always appear active, even if the application is not in the foreground. Also, whenever we decide to display a message box or a normal dialog box, the main window and tool windows will still appear active, when in this scenario we ideally want to make them look inactive.

This calls for a more careful study of window activation messages. The list below describes the series of window activation messages sent when one window becomes active, and another inactive.

WM_MOUSEACTIVATE is sent to the window to ask it whether or not the activation request should be allowed. The return value (i.e. MA_ACTIVATE or MA_NOACTIVATE) effects the subsequent activation messages.

WM_ACTIVATEAPP is sent when a window belonging to a different application is about to become active (or inactive). The return value should always be zero, and never effects subsequent message's behaviour.

WM_NCACTIVATE is sent when a window's non-client area needs to be activated or deactivated.

WM_ACTIVATE is sent last of all, to activate the window itself. The default window procedure sets the input focus to the window being activated.

In addition to these messages, I shall describe a couple of scenarios to get you thinking.

  1. With all of these activation messages, only two windows are actually involved - the window being deactivated, followed by the window being activated. So, even if we have many floating tool windows, not all of them will be affected by window activation. This means that:
  2. When our application is activated / deactivated, ALL toolwindows need to be updated to keep in sync with each other - either to be shown as activate or inactive.
  3. This also applies to activation within our own application - all toolwindows need to be kept in sync.
  4. When the main window is disabled due to a modal dialog or message box being displayed, then the tool windows (or any modeless dialogs) will remain unaffected. It is usually desirable to enable / disable all tool windows in a group to prevent the user from interacting with them whilst a modal dialog is on screen.

Popup window list

In order to keep all popup windows (including the main window) synched to the same state, we need to be able to identify all popup windows at any one time. Therefore we will define a function, GetPopupList, which will return a list of all floating popup windows that we want to keep synchronised. The function will look like this:

?
int GetPopupList(HWND hwndMain, HWND hList[], DWORD nSize, BOOL fIncMain);

hwndMain specifies the parent or owner window of the popups that we want to return.

hList is the address of an array of window handles (HWNDs), which is where we will store the list of popup windows.

nSize specifies the number of elements in that array (so we don't accidently overflow it).

fIncMain is a boolean flag, which specifies whether or not to include the main window (which is also a popup window) in the list.

There are many different ways to implement this function. In the source-download, I simply keep a private list of floating ToolWindows, and copy that into the array provided.

?
int GetPopupList(HWND hwndMain, HWND hList[], DWORD nSize, BOOL fIncMain)
{
    int i, count = 0;
    // just copy our private list of popups.
    // Also check to see that each popup is owned by hwndMain.
    for(i = 0; i < nNumDocWnds && i < nSize; i++)
        hList[count++] = hPrivate[i];
    if(fIncMain)
        hList[count++] = hwndMain;
    return count;
}

Depending on how you organise the windows in your project, you may need to provide a different implementation of this function. Another method to get a list of popup windows is to use the EnumThreadWindows API call, and iterate over the popup windows which are owned by the current process. This would also return all windows such as dialog boxes, and non-toolbar windows, so would require more careful coding.

Generic WM_ACTIVATE handler

Our first stab at a solution will be to concentrate on the WM_ACTIVATE message. This message is received whenever a window is activated or deactivated. The direction we will take will be to decide if the window receiving this message is active or inactive, and synchronise all other windows to the same state by sending them a "spoof" WM_NCACTIVATE message. This spoof message will force the other windows to update their titlebars to the same state as the window receiving the WM_ACTIVATE.

?
LRESULT HANDLE_ACTIVATE(HWND hwndMain, HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    HWND hwndList[MAX_DOCK_WINDOWS+1];
    int i, nNumWnds;
    BOOL fKeepActive = (wParam != WA_INACTIVE);
    // find list of popups
    nNumWnds = GetPopupList(hwndMain, hwndList, MAX_DOCK_WINDOWS+1, TRUE);
    // Sync all other popups to the same state
    for(i = 0; i < nNumWnds; i++)
        SendMessage(hwndList[i], WM_NCACTIVATE, fKeepActive, 0);
    return DefWindowProc(hwnd, WM_ACTIVATE, wParam, lParam);
}

It works, after a fashion. All popups activate and deactivate correctly, and all at the same time. This solution is not the best though.

The problem is that every popup window's titlebar flashes whenever the active window changes. This is because of the way Windows sends the WM_ACTIVATE message. This message is sent first of all to the window that is being deactivated. This causes our handler (above) to deactivate all the popup windows. WM_ACTIVATE is then received for the active window, which then (correctly) activates all the popup windows. It is this anomaly that causes all the windows to flash.

A partial solution is to perform a check before synchronising all the popup windows. We know that if a window is being deactived, lParam identifies the window being activated. And if this window is one of our popup windows, we can completely skip synchronizing all the windows, because the popup window that is about to become active will do this anyway.

?
if(fKeepActive == FALSE)
{
    for(i = 0; i < nNumWnds; i++)
        if(hwndList[i] == (HWND)lParam)
            return DefWindowProc(hwnd, WM_ACTIVATE, wParam, lParam);
}

This prevents every popup window from briefly deactivating, then activating again. There is still a problem, albeit a minor one. The problem is, the single window that is being deactivated will still flicker briefly before being activated again. This is because it will already have received it's WM_NCACTIVATE message, which caused the window to deactivate. The window gets it's activated look eventually, but this brief flicker is still visible.

Generic WM_NCACTIVATE handler

We need to take a step back and approach the problem from a slightly different direction. Instead of handling WM_ACTIVATE, which is called after a window's titlebar is redrawn, we will go straight to the heart of the problem. We will write a handler for the WM_NCACTIVATE message which will ensure that no unnecessary activation or deactivation will take place.

The function presented below can be called from any window that wants to be kept active alongside all the popup toolbars. The only requirement on your part is to provide a suitable GetPopupList which includes all these windows.

The handler will perform several tasks, listed below.

  1. Retrieve a list of current popup windows.
  2. Search the list for the other window being activated/deactivated in our place (the window specified by lParam). If this other window is a toolwindow, then we need to remain active.
  3. Synchronize all current popup windows to our (possibly new) state.
  4. Activate/deactivate ourselves, depending on our new state.

The code looks like this:

?
LRESULT HANDLE_NCACTIVATE(hwndMain, HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    static HWND hwndList[MAX_DOCK_WNDS];
    int i, nNumWnds;
    HWND hParam = (HWND)lParam;
    BOOL fKeepActive = wParam;
    BOOL fSyncOthers = TRUE;
    nNumWnds = GetPopupList(hwndMain, hwndList, MAX_DOCK_WNDS, TRUE);
    // UNDOCUMENTED FEATURE:
    // If the other window being activated/deactivated
    // (i.e. NOT this one) is one of our popups, then go (or stay) active.
    for(i = 0; i < nNumWnds; i++)
    {
        if(hParam == hwndList[i])
        {
            fKeepActive = TRUE;
            fSyncOthers = FALSE;
            break;
        }
    }
    // If this message was sent by the synchronise-loop (below)// then exit normally. If we don't do this, there will be an infinite loop!
    if(hParam == (HWND)-1)
    {
        return DefWindowProc(hwnd, WM_NCACTIVATE, fKeepActive, 0);
    }
    // This window is about to change (inactive/active).// Sync all other popups to the same state
    if(fSyncOthers == TRUE)
    {
        for(i = 0; i < nNumWnds; i++)
        {
            // DO NOT send this message to ourselves!!!!
            if(hwndList[i] != hwnd && hwndList[i] != hParam)
                SendMessage(hwndList[i], WM_NCACTIVATE, fKeepActive, (LONG)-1);
        }
    }
    return DefWindowProc(hwnd, WM_NCACTIVATE, fKeepActive, lParam);
}

The code above uses an undocumented feature of the WM_NCACTIVATE message which I observed whilst experimenting with these activation messages. The MSDN documentation states that lParam is unused (presumably zero), but this is not the case under Windows 95,98,ME, and NT,2000,XP.

Instead, lParam is a handle to the other window being activated/deactivated in our place. (i.e. if we are being deactivated, lParam will be the handle to the window being activated). This is not always the case, specifically when the other window being activated/deactivated belongs to another process. In this case, lParam will be zero.

Updated 31-8-2006: It has been brought to my attention that lParam will be NULL when the other window being activated/deactivate belongs to another thread - not just a different process. Therefore care should be taken when using this technique - all toolbars / windows involved in this activation scheme must belong to the same thread.

Generic WM_ENABLE handler

This task is optional, but will make your application slicker if you do include this functionality. This message handler is required for the following situation. Whenever a modal dialog box or message box is displayed, the main application window (and all it's children) become disabled. This feature prevents the user from clicking on and activating the main window whilst the modal dialog is displayed.

However, popups, modeless dialogs, or any floating window will not be disabled. This means that the user can quite easily activate these windows whilst a modal dialog is displayed. In my opinion, this is not a good idea because the whole idea of a modal dialog is to present the user with a single, uninterruptable task. The solution is to write a simple WM_ENABLE handler which we can call from the main window procedure. Whenever a WM_ENABLE is received (either to enable or disable), the handler simply enables/disables all the current floating toolwindows to the same state.

?
LRESULT HANDLE_ENABLE(hwndMain, HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    static HWND hwndList[MAX_DOCK_WNDS];
    int i, nNumWnds;
    HWND hParam = (HWND)lParam;

    nNumWnds = GetPopupList(hwndMain, hwndList, MAX_DOCK_WNDS, FALSE);
    // Synchronise all toolwindows to the same state.
    for(i = 0; i < nNumWnds; i++)
    {
        if(hwndList[i] != hwnd)
        {
            EnableWindow(hwndList[i], wParam);
        }
    }
    //just do the default
    return DefWindowProc(hwnd, WM_ENABLE, wParam, lParam);
}

Using the handler in your application

In order to use the WM_NCACTIVATE message handler in your own application, you need to do two things. Firstly, you need to call the handler from ANY window that you want to keep active along side another. This includes the main window, and any popup windows that you to keep synchronised. The second requirement is to ensure that the GetPopupList function returns the correct list of popups for your particular application.

Special care needs to be taken when calling HANDLE_NCACTIVATE. As well as passing the window handle for which the message was received (the second parameter), the top-level owner window also needs to be specified, as the first parameter. For the top-level window in your application, the same window handle must be used for the first and second parameter:

?
case WM_NCACTIVATE:
    return HANDLE_NCACTIVATE(hwnd, hwnd, wParam, lParam);

On the other hand, a popup window would call the handler slightly differently. The main top-level window still needs to be specified as the first parameter, so this window needs to be retrieved for any popup window whilst processing this message:

?
case WM_NCACTIVATE:
    return HANDLE_NCACTIVATE(GetOwner(hwnd), hwnd, wParam, lParam);

The sample application in the source-download shows you exactly how to use the handler in your own applications, so you shouldn't have any problems figuring it all out.

Conclusion

Hopefully you have learnt how to create "proper" floating tool windows. This technique has been around since the days of Windows 3.0 (and probably earlier), but clear information on the subject always seems to be difficult to find.

This article went on a bit longer than I originally wanted it to. However, I felt it was important to cover all the angles, to give the reader a better understanding of how windows really works.

The next article in this two-part series will show you how to make these floating tool windows into dockable windows, so stay tuned!

20 Nov 2001

Posted in Tutorials

win32
Powered by Drupal


Docking Toolbars - Part 2 | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
dock2.zip

Published on 22 Jan 2012

20.8 Kb

Download

Docking Toolbars - Part 2

How to implement docking toolbars

Updated 3 May 2005

The following projects are enhancements of the original docking code found in this tutorial.

  • Docking Windows using DWinLib by David O'Neil www.randommonkeyworks.com
  • Docking Toolbars by Jeff Glatt www.codeproject.com/useritems/dockwnd.asp

Introduction

After a long delay I've finally finished part 2 of the Docking Toolbar series. The previous article took you through the steps necessary to create floating tool windows. This article will discuss the techniques necessary to get these floating windows to "dock" with a main window. I'm not going to present must source-code in this tutorial, because quite alot is involved. Of course, you can download the code using the link above, but I'm going to take a higher-level approach with this tutorial.

{short description of image}

What is involved?

First of all I need to define the terms "Docked" and "Undocked". A window is docked when it is a child of another window. i.e. it is contained by some parent window, which moves and sizes it appropriately when the main window is resized. It is important to understand that it is the parent window which is responsible for positioning the "docked" window. On the other hand, a window is undocked (or floating) when it is a popup window. i.e. the tool window still has an owner, but it is not contained in any way.

There are many different ways to implement docking windows. This is because there is no standard, built-in docking window support in Windows. The result of this is that application developers have had to implement their own docking windows, or rely on 3rd party libraries to do the work for them (such as MFC).

However, there are two major types of docking window which you can choose from. The most common (and intuitive, in my opinion) is the type that you drag around the screen using the mouse. When you move one of these windows, instead of the window moving, a drag-rectangle (feedback rectangle) is XORed on the screen, showing the outline of where the window will move to when you release the mouse - like the way windows work when full-window dragging is turned off. With this method, when a window is dragged to / from a window, the feedback rectangle visibly changes to indicate that the window can be dropped. This is the docking behaviour that I will be presenting in this tutorial.

The second type of docking can be found in some newer style applications (such as Microsoft Outlook). Instead of a feedback rectangle, windows can be directly "teared" or "snapped" on or off a main window - i.e, they snap into place as soon as you manipulate them. Personally, I don't like this type of user-interface, so I'm not going to do any work on this.

Providing an Intuitive User Experience

Docking toolbars are so common that it is natural to stick with the same conventions as other applications. Our docking windows will have the following characteristics:

  • A docking window will use a feedback (drag) rectangle to indicate it being moved around the screen - even if the "full window drag" system setting is in operation.
  • When a docking window is dragged around the screen, at some point it will intersect the main application window. When this happens, the feedback rectangle will need to visibly change in order to reflect the fact that there is a docking "region". Normal convention is for a wide (say three pixel) shaded border to represent a floating position, and for a single-pixel border to represent a docked position.
  • When the mouse is released after dragging a dock-window, a test must be made to see if the window should be made to dock or float.
  • A docking window can be forced to float, even when the drag-rectangle is released over the main window. This is usually achieved by holding the <Control> key down.
  • A docking window can be resized when it is floating, because it is just a normal window. No special processing is required to do this - the standard Windows sizing behaviour can be used in this case.

Now that we have decided what our goal is, the next step is to start coding!

Docking and Undocking a window

The first problem you will encounter is trying to make a window toggle between being a child window and a popup window. This is not as easy as it sounds, but can be accomplished with the code shown below.

?
void ToggleDocking(HWND hwnd)
{
    DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
    if(dwStyle & WS_CHILD)
    {
        /* toggle from WS_CHILD to WS_POPUP. */
        SetWindowLong(hwnd, GWL_STYLE, (dwStyle & ~WS_CHILD) | WS_POPUP);
        SetParent(hwnd, NULL);
    }
    else
    {
        /* toggle from WS_POPUP to WS_CHILD */
        SetWindowLong(hwnd, GWL_STYLE, (dwStyle & ~WS_POPUP) | WS_CHILD);
        SetParent(hwnd, GetWindow(hwnd, GW_OWNER));
    }
}

Look at the first SetParent API call in the code above. The only way to make a child window into a popup window is to set it's parent window to zero (NULL). This may seem as if the popup window no longer has a parent, and won't float above the main window anymore. However, the tool-window will always have an owner window (the parent window specified during creation), even if it's parent is subsequently set to NULL. Therefore the toolwindow will always float above the main window, when it has the WS_POPUP style.

Tool Window Frames

The purpose of a frame window is to simply contain some other window or windows within it. In the case of our dockable tool windows, we will use a container window to hold our actual Toolbar or TreeView, or whatever it is that we want the user to interact with.

Bearing this in mind, it doesn't matter if the tool window is docked or undocked - the important part (the contents) will always be a child window contained in a frame. This means that the child window (a dialog template, TreeView, whatever) doesn't need any special attention because the docking behaviour is now entirely separate from the contents. This is good software design, because it allows us to define distinct software elements which do not need to be dependent on each other.

At this stage you must make a decision. There are basically two types of window "management" you can choose from:

  1. Host and docked tool-windows directly in the application main window (easiest to implement, less code). This does mean that the main window has be able to manage an unknown number of docked windows - size them, reposition them etc, which could make a larger application a bit more fiddly to code.
  2. Create a second type of window, a "tool window container". This container window would manage any docked windows placed on it - sizing them, looking after them etc. The container window would always be present in the main frame-window, so the main application code would only have to worry about this one window. This method would probably provide for a better designed application in the long-run, but does require more careful thought (and more coding).
{short description of image} | {short description of image}
Method 1 | Method 2

The choice is up to you, really. The docking library you can download from this page uses the first type of docking window, but depending on how you want to implement your application, you might want to decide to opt for the second.

Create a docking window

There are many different ways to create a docking window. I imagine most of the code on the Internet uses well-designed C++ classes to hide the implementation. However, I still prefer to code in C, so the design of this library will be a non-object oriented approach.

First, some assumptions must be made. We are creating docking windows, right? Docking windows can be in one of two states: docked, or undocked. This means that we need to remember where the docking window is, so that when we dock/undock the window, it is always positioned in the right place. Also, when we quit our application and restart it, the user will normally expect his/her window arrangement to be exactly the same. So, we need some kind of structure to track the state of a docking window, don't we? (Yes, we do!)

?
typedef struct
{
    DWORD dwStyle;        //styles..
    int xpos;           //coordinates of FRAME when floating
    int ypos;
    int cxFloating;     //size of CONTENTS when floating
    int cyFloating;
    int nDockedSize;    //width/height of CONTENTS when docked..
    BOOL fDocked;        //is window docked?
    HWND hwndContents;   //handle to the window to host} DockWnd;

This structure will be our starting point for a docking window. For every docking window that we want to create, we must maintain a unique DockWnd structure, which describes the docking window state. This structure would probably be a global variable. So, even if the docking window is hidden (or has been destroyed), we always know the exact state that it should be recreated in.

There are basically two main groups of information that we use in one of these structures: floating position, and docked position.

When a dock-window is floating, we need to know it's coordinates on the screen, and also it's size. We actually keep track of the size of the dock-window contents (it's child window) instead of the physical size of the dock-window. This is because we always want the contents to remain the same size, even when the system settings change (i.e. the titlebar height is modified using Control Panel).

When a dock-window is docked, we only need to know one dimension: the width (or height) of the window. We need just one dimension, because when a window is docked (say at the top of the main window), it will be stretched to fit the width of the main window. It is only the height of the docked window that can be represented in our structure (using the nDockedSize member).

The following function will create a docking window. I don't want to describe any coding details, because it's all in the source-download.

?
HWND CreateDockWnd(DockWnd *dwp, HWND hwndParent, TCHAR szCaption[]);

The first parameter must be the address of one of our DockWnd structures. This structure must be already set up correctly before calling CreateDockWnd. The function call creates and positions the docking window, based on the members in the structure. Below is a small example of how you would use the function call.

?
DockWnd dock1;
...
/* Set the DockWnd structure up */
dock1.dwStyle      = 0;
dock1.xpos         = 123;
dock1.ypos         = 456;
dock1.cxFloating   = 200;
dock1.cyFloating   = 100;
dock1.nDockedSize  = 80;
dock1.fDocked      = FALSE;     /* Make window float initially */
dock1.hwndContents = hwndEdit;  /* Some child window *//* Create a docking window! */
HWND hwndDock1 = CreateDockWnd(&dock1, hwndMain, "Docking Toolbar #1");

Now that we can create a docking window, we can move on and give it a bit of functionality.

Moving a window with a drag-rectangle

The first obstacle you will encounter is getting Windows to show a feedback rectangle when you move a floating window around. Starting with Windows 95, a new user-interface feature was introduced. This is feature is normally referred to as "Show window contents whilst dragging". When enabled, windows are no longer moved and sized using the standard feedback rectangle.

Unfortunately there is no way to turn this feature off for specific windows.The SystemParametersInfo API call, (with the SPI_GETDRAGFULLWINDOWS setting) can turn this feature on and off, but this is a system-wide setting, and is not really suitable. Of course, we could devise a method where we temporarily turned off the drag-window system setting when just during the window movement (actually, this would be very straight-forward). The point is, it's a bit of a hack, and I prefer proper solutions to problems like this.

The only solution is to override the standard windows behaviour and manually provide a feedback rectangle. This means processing a few mouse messages. Now, I don't want to show any code - again, the source-download clearly demonstrates how to get this working. What I will do is give a basic outline of the processing that is required.

Prevent default window dragging

The most important task is to stop the user from dragging the window around with the mouse. I know this sounds counter-productive, but we need to completely take over the standard window movement logic. This is actually quite simple - our docking window procedure just needs to handle WM_NCLBUTTONDOWN, and return 0 if the mouse is clicked in the caption area. By preventing the default window procedure from handling this message, window dragging is completely disabled.

Moving the drag-rectangle

In order to simulate the window being moved, we need to handle a few mouse messages. Only three need processing:

  1. WM_NCLBUTTONDOWN - in addition to what was described above, this message handler must draw the feedback rectangle, and set the mouse capture, using the SetCapture API call.
  2. WM_MOUSEMOVE - this message will be received whenever the mouse is moved. Our response will be to redraw the feedback rectangle in the new position (erase it in the old position and draw it in the new position). In addition, we need to decide what type of rectangle to draw, depending on if the current feedback rectangle will result in a docked / floating window position when the mouse is released.
  3. WM_LBUTTONUP - when the mouse is released, we need to remove the feedback rectangle from the screen, and then take the appropriate action to physically reposition the dock-window. This may mean docking / undocking, or simply moving the window if it was already floating.

As you can see, there's a little bit of work involved, but nothing particularly complicated. The big advantage of using this method is that the same mouse code can be used when the window is docked or floating - this keeps the code short and simple.

Drawing a drag-rectangle

A drag-rectangle is basically just a simple rectangle. This rectangle ideally needs to be drawn using XOR blitting logic - so that we can easily draw / erase the rectangle as it is moving around.

{short description of image}

The code below draws a shaded rectangle with the specified coordinates. The equivalent function in the source-download does a little more than the code below (it draws both types of drag-rectangle), but I've stripped it down to keep it simple.

?
void DrawXorFrame(int x, int y, int width, int height)
{
    /* Raw bits for bitmap - enough for an 8x8 monochrome image */
    static WORD _dotPatternBmp1[] =
    {
        0x00aa, 0x0055, 0x00aa, 0x0055, 0x00aa, 0x0055, 0x00aa, 0x0055
    };
    HBITMAP hbm;
    HBRUSH hbr;
    HANDLE hbrushOld;
    WORD *bitmap;
    int border = 3;
    HDC hdc = GetDC(0);
    /* Create a patterned bitmap to draw the borders */
    hbm = CreateBitmap(8, 8, 1, 1, _dotPatternBmp1);
    hbr = CreatePatternBrush(hbm);
    hbrushOld = SelectObject(hdc, hbr);
    /* Draw the rectangle in four stages - top, right, bottom, left */
    PatBlt(hdc, x+border,       y,               width-border,  border,        PATINVERT);
    PatBlt(hdc, x+width-border, y+border,        border,        height-border, PATINVERT);
    PatBlt(hdc, x,              y+height-border, width-border,  border,        PATINVERT);
    PatBlt(hdc, x,              y,               border,        height-border, PATINVERT);
    /* Clean up */
    SelectObject(hdc, hbrushOld);
    DeleteObject(hbr);
    DeleteObject(hbm);
    ReleaseDC(0, hdc);
}

Decide when a window can be docked

When a docking window is repositioned (basically, dragged around using the mouse), it's docking status may need to be toggled. The problem is, this decision making is probably going to be different from application to application - when and how a window is able to dock is really up to the programmer. A simple strategy to start with might be this: if the dock-window intersects (overlaps) the main window, then it is eligible to be docked with that window, otherwise it will be made to float (i.e. when it does not overlap the main window).

This decision will have to be made many times during the repositioning of a dock-window. Therefore, we will introduce a new windows message, which we will send via the WM_NOTIFY message:

?
#define DWN_ISDOCKABLE ... /* some value that doesn't already exist */

This message will have one parameter (sent through lParam) - a pointer to a DockWndQuery structure, defined below:

?
typedef struct
{
    HWND hwndDock;      /* Handle to dock-window */
    DockWnd *pDockWnd;      /* Pointer to the DockWnd structure for that window */
    RECT    *dragrect;      /* Current drag-rectangle */
} DockWndQuery;

A dock-window will send this message to it's parent (or owner) window when it is being repositioned and needs to know if the drag-rectangle position will result in the dock-window being docked or floating. It will be the responsibility of the parent window (normally, the main frame window) to handle this message.

I have provided a "helper" function, DockWnd_GetDockSide, which can be used to handle this special message. Just look in the sample application download for more information.

Conclusion

That's about all for the moment. The source-code download presents a fairly complete docking window library, and a small sample application to show you how to use it. Hopefully you should be able to plug this library straight into your applications and get instant docking windows!

I realise that this is quite a tricky subject area, and unfortuntately there is no simple solution to the problem. Anyway, I'd definitely like have your feedback, to make sure I've covered enough material. Although the source-code is fairly well commented, it may not be optimal. If you make any improvements, be sure to let me have a copy! Remember as well, that this is just one of the many methods of implementing docking windows. Even if my design doesn't fit into your particular scenario, you should get enough ideas from the code to implement your own framework.

30 Jun 2002

Posted in Tutorials

win32
Powered by Drupal


Memory Techniques - Part 1 | Catch22

!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd"> Memory Techniques - Part 1 | Catch22
Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
hexview01.zip

Published on 22 Jan 2012

11.02 Kb

Download

Memory Techniques - Part 1

Memory management techniques for large file editors

This article came about due to interest in the way my HexEdit application manages large files, but also my desire to document what I consider to be the core technology behind HexEdit. I will be describing not only the memory management techniques for loading multi-gigabyte files, but also the techniques I developed to allow extremely fast insertion and deletion from these large files.

The Scenario

The problem we are trying to overcome is simply this: You want to develop some kind of file or text editor, and you want your program to be able to load, edit and save files of any size. In fact, you want your program to load and edit the largest sized file possible. Under 32bit Windows this is a 4Gb file. (Actually, Windows NT supports 18 exabyte files with its 64bit NTFS file system, but I will talk about this type of file later in the series.

Your file editor needs be able to load a 4Gb file in a fraction of a second. In addition, insertions and deletions to and from the file must be as fast for a 4Gb file as they are for a 1Kb file.

This series of articles (I haven’t decided exactly how many articles there will be yet) will take you through the design decisions and algorithms necessary to accomplish these goals. At the end of the series you will have enough information to develop your own file-handling functions, which will be as powerful and flexible as those used in my HexEdit application.

Memory Management

Let’s forget about actually editing a large file for the moment, and concentrate just on loading a file into your application. So, your have a 4Gb file saved on your hard disk, and you want to load this file so you can preview any portion of it. Let’s assume that our first application will be a simple hex file viewer, with no editing capabilities.

The first decision to take is what memory facilities we will use to gain access to the file’s contents. To make this decision, we need to know what memory facilities are available to our program, and base our decision on that. Because our file viewer will be a 32bit Windows program capable of running under Windows 9x and NT / 2000, we have the following facilities at our disposal.

I will make a note here for you to go and read "Advanced Windows" by Jeffrey Richter. This book covers the entire memory architecture and memory management in Windows, and will be much more detailed than what I will be describing here.

Virtual Memory

Every 32bit process in Windows has a 4Gb virtual address space, regardless of the amount of physical RAM you have installed. Of this total virtual space, only 2Gb is available for an application’s private use. Actually, the available space slightly less than this - 128Kb less in WinNT and 4Mb less in Win9x).

There are a variety of methods to allocate memory from this 2Gb region of space. All depend on how much memory needs to be allocated, and what its intended use is.

  1. malloc and new[]The simplest is to use the standard C or C++ memory allocation routines (malloc and new[], respectively). Both of these methods use the memory allocation facilities provided by the operating system. In Visual C++ 6.0, malloc and new[] use VirtualAlloc internally. Anyway, malloc and new[] are only really meant for allocating lots of small objects, not one gigantic memory region.
  2. HeapAllocThe second method to allocate memory in Windows is to use the Heap function – HeapAlloc (or the older LocalAlloc and GlocalAlloc). These functions are also meant for allocating many small objects though - just like malloc and new[] - and are also implemented by calls to VirtualAlloc.
  3. VirtualAllocThe third way to allocate memory in Windows is to use VirtualAlloc directly. This method is much more flexible because it allows you to specify where you want the memory to be in your address space, and allows you to reserve a region of memory without actually allocating any space from it.
  4. Memory-Mapped-FilesThe forth way to allocate memory is by using Memory Mapped Files. Rather than allocating memory, a memory-mapped file is a reserved region of the 2Gb address space, which has mapped into it a region a file stored on disk. The great thing about memory-mapped-files is that they free the programmer from having to do any direct file reading or writing. Simply reading and writing to the memory-mapped view causes the file's contents to be accessed. The operating system provides clever page caching to make memory-mapped files a very quick way to read/write to a file.

The picture below should help to describe the relationship between the various memory-allocation facilities. The important thing to remember is that all four types of memory allocation provide access to an application's virtual address space in one way or another.

I won’t go into any more detail because there is a problem, which is this. With a maximum of 2Gb available, how are we going to allocate enough memory to access our 4Gb file? The answer is simple. It is impossible to allocate more than 2Gb. At this point, you might decide to lower your sights a little, and say that the biggest file we will load in our file viewer will be 2Gb in size. Whilst this is what many hex-editor programs do, this is not good enough for us, is it?

The only solution will be to access the file in sections, as we need them. This has one big disadvantage, which is that, our algorithms to access / search a file become more complicated because we no longer have one contiguous memory region to operate with. There are three major advantages to balance this one disadvantage though.

  1. Because we have decided to access a file in sections, we don’t need to allocate very much memory any more. If we access our file in blocks of 64kb for example, we only need one 64kb block of memory in use at any one time. This leaves a vast proportion of our 2Gb address space for other interesting things.
  2. Reading and writing a file from disk becomes extremely fast. We now only read what we want, when we want it. If we had tried to load the whole file at one go, this could have taken a long time if the file was very large. Because the block size is so small in comparison, file load time becomes so fast that you will probably never notice it happening.
  3. Because we are using such a small amount of memory to access the file, all of the memory facilities that we discarded as being inadequate can be revisited. In fact, any one of the four memory facilities is suitable for our purposes.

An example file viewer

Now it’s time to present an example. We will use memory-mapped-files as the technique to access the file’s contents, simply because they are so flexible. Because of this decision, we must also use API calls to open and close the file. I’ll take you through the steps to create the backbone of the file viewer.

A file viewer will typically display a tiny portion of a file in its main window. Whenever the main window needs to redrawn, the file viewer needs to retrieve the portion of the file which is currently being viewed. Most of the memory reads will be to one local area in the file. As long as our memory-mapped view of the file is big enough, then we will very rarely need to re-map the view to another area of the file. 64Kb will certainly be big enough in our case.

Only when the file-viewer scrolls up and down through the file will we potentially need to re-map our view of the file.

The diagram below should help to explain how a 64kb window is used to access a large file’s contents. A user of the view (such as our hex-viewer application) is free to access any part of the file within the view. Any accesses outside the range of the view require the view to be re-mapped to that part of the file. Failure to do so would result in a fatal application error.

The fact that the file is accessed through a small window should be irrelevant to the display portion of the hex file viewer. All the drawing code requires is a small amount of memory every time the visible window needs to be re-drawn. The diagram above clearly illustrates a division between the window that displays the file, and the memory-mapped window that exposes the file’s contents.

This relationship is basically the same as the standard MFC Document / View design, and it is this design that we will use for our file viewer. This means that our underlying implementation of the "Document" can change at any time. The "View" (the visible window) doesn’t ever need to know how the Document works – it just requests small amounts of data at a time to satisfy its drawing requirements.

Document Design

The "Document" portion of our hex-viewer will be implemented as a C++ class. I chose C++ because it is much easier to perform object encapsulation than just using plain C. I don’t intend to use any advanced features of C++ such as templates or inheritance, so feel free to convert the class into a C structure if you really can’t stand C++.

The first thing to draw your attention to is the data-type I use to represent the size of files and sequences. I created a new typedef called size_w, which is currently an unsigned integer. This type has to be unsigned, so that the full 4Gb range can be represented. At some time in the future this type could be changed to an unsigned 64-bit integer. With a simple recompile, the sequence would be capable of handling huge 18 exabyte files (18 thousand thousand megabytes!).

This is what the class interface will look like:

?
class sequence
{
public:
    sequence();
    ~sequence();
    BOOL init(char *filename);
    BOOL close();
    size_w size();
    BOOL render(BYTE *buffer, size_w offset, size_w length);
private:
    BYTE *get_adjusted_ptr(size_w offset);
    // private implementation details
    HANDLE hMemMap;            // memory mapped object
    HANDLE hFile;              // handle to current file
    size_w length;             // logical size of this sequence
    size_w filebufferlength;   // size in bytes of the entire file
    BYTE *filebuffer;          // base of the view of the file
    BYTE *adjustedptr;         // an adjusted version of the filebuffer pointer
    size_w mappedoffset;       // offset of the current view within the memmap object
    size_w mappedlength;       // length of the view};

As you can see, the design is very simple. The constructor and destructor don’t really do an awful lot apart from setting everything to zero and cleaning up, so I’ll start by describing the init() member function.

Initialising the sequence with a file

The init() member function simply opens a file, and creating the file mapping which spans the length of the whole file. The rest of the work is resetting all the member variables to their default values. Note that no view of the file has been created at this stage.

?
BOOL sequence::init(char *filename)
{
    HANDLE hTemp;
    // try to open the specified file for read-only access
    hTemp = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, 0,
            OPEN_EXISTING, 0, 0);
    // if we failed to open the file, then exit now and keep the current file open
    if(hTemp == INVALID_HANDLE_VALUE)
        return FALSE;
    // now close the CURRENT file, and let the new file become the current one
    close();
    hFile = hTemp;
    // create a file mapping which spans the entire file.
    // This works even for the 4Gb file sizes
    hMemMap = CreateFileMapping(hFile, 0, PAGE_READONLY, 0, 0, 0);
    length = GetFileSize(hFile, 0);
    filebufferlength = length;
    return TRUE;
}

Requesting a pointer to the file's contents

The "brains" behind the sequence object is the get_adjusted_ptr member function. This function is a private member, so it can't (and doesn't need to) be called from outside the sequence class.

?
BYTE *sequence::get_adjusted_ptr(size_w offset)
{
    size_w baseoff = calc_index_base(offset);
    // if we already have the right range of memory mapped in
    if(adjustedptr != 0 && mappedoffset == baseoff)
        return adjustedptr;
    //otherwise, map in the new area
    else
    {
        if(filebuffer)
            UnmapViewOfFile(filebuffer);

        mappedlength = min(filebufferlength - baseoff, MEM_BLOCK_SIZE);
        filebuffer = (BYTE *)MapViewOfFile(hMemMap, FILE_MAP_READ, 0, baseoff, mappedlength);

        mappedoffset = baseoff;
        adjustedptr = filebuffer - mappedoffset;
        return adjustedptr;
    }
}

The idea behind get_adjusted_ptr is to hide the details of the memory-mapped file from the rest of the class. As far as the rest of the class is concerned, all get_adjusted_ptr does is to return a pointer to the contents of the current file. This pointer can then be used to access the file using simple array subscripting.

There is one very important detail to mention. The pointer that is returned is ADJUSTED, so that it ALWAYS points to the first byte in the file, regardless of what portion of the file is mapped into memory. Think about this carefully and re-read the previous sentence if you didn't understand it.

This may seem a little confusing at first, but read on anyway.

When a 64Kb view of the file is mapped in (aligned to a 64Kb boundary of course), the pointer to the view would be relative to the start of the view, and not the start of the file. This presents a problem, because ideally we want to access the file in as normal a way as possible. By subtracting the current view-offset (the position of the memory-mapped view within the file) from the pointer we get from MapViewOfFile, we get an adjusted pointer to the start of the file.

Let's use a very small example. We want to access a region of the file at offset 0x56789. We request a pointer to the file's contents like this:

?
BYTE *filestart = get_adjusted_ptr(0x56789)

Now we have a 64Kb window which encompasses offset 0x56789, through which we can access the file's contents. Note that we are allowed to read at most 64Kb past this pointer. Let's read some memory from this region of the file.

?
BYTE buffer[200];
memcpy(buffer, &
filestart[0x56789], 200);

Now comes the magic part (bear with me here!). We are adding 0x56789 back onto the adjusted pointer. The result of this is a pointer to the start of the current view, so we are back where we started. The memory is read from the mapped region, instead of from the start of the file, and we now have a nice intuitive way to access the file's contents as if it were one contiguous block of memory.

Ok, it doesn't seem like much, and you may even be wondering why I've made such alot of work for myself.

The simple fact of the matter is that the pointer arithimetic has to be performed at some stage, simply because we are accessing the file in sections. It just makes sense to hide the arithmetic in one single place, at an early stage, and let the rest of the class design be blissfully un-aware that any pointer arithmetic has to be done at all.

What section do we map in?

A view of a memory-mapped file MUST be aligned to a 64Kb boundary, otherwise MapViewOfFile will fail. So, we can't simply map a view of the file at any old offset (such as 0x56789). We need some method to determine the closest 64Kb boundary to the desired access offset, and create a view at this offset instead. This is what the calc_base_index function does.

calc_base_index takes one parameter (an offset or index), and returns a new offset which is on a 64Kb boundary. i.e. the number returned is a multiple of 65536 (0x1000).

Now, you might just think that the following formula works pretty well:

?
offset = offset & 0xffff0000;

What this does is round offset down to the nearest 64Kb boundary, which is exactly what we want.

It's not perfect though, for the following reason. Assume that the input offset ranges from 0 to 0xFFFFFFFF. The simple formula above provides us with consequetive 64Kb offsets, one after the other. This problem with this approach is this: What happens when we scroll down the file in our hex-view window? We reach a point when we come to the end of one mapped view, and the next view needs to be mapped in. No problems so far. The formula above provides us with the next offset to map in. The problem occurs when we want to scroll back. We need to re-map the previous section in, just after we mapped the new one! And what happens if we scroll forwards again?

What we really need is a formula which instead of rounding down to the nearest allocation unit, rounds to the middle of the nearest unit. So, if we request a some memory at an offset which is exactly on a 64Kb boundary, we don't get a memory-region starting at this offset - instead, the memory region starts half-way back to the previous 64Kb boundary. This gives us freedom to move backwards and forwards from the requested memory address, safe in the knowledge that we have half a block-size either side to play with. This has an effect - now we are calculating offsets every 32Kb. All this means is we need to increase our window size to 128Kb to make sure we still get offsets 64Kb-aligned, but that's no problem.

?
size_w inline calc_index_base(size_w index)
{
    if(index < MEM_BLOCK_SIZE / 2)
    {
        return 0;
    }
    else
    {
        return ((index + MEM_BLOCK_SIZE / 4) &
               (~(MEM_BLOCK_SIZE / 2 - 1))) - MEM_BLOCK_SIZE / 2;
    }
}

The formula is especially nasty (it took me a while to come up with it I can tell you!). But, not only does it give us 64Kb aligned offsets, but the sections that we get from it are rounded to the centre of our input offset, not the beginning. This means that our adjusted pointer can move backwards and forwards by 32Kb without causing any views to be remapped.

If you really don't like the function above then just use "return index & 0xffff0000". The work's been done for you though, so you might as well take advantage of it.

Feel free to sit down with a pen and paper and figure out how it works!

Rendering the sequence

The rest pf the work is collected in the render() member function. This function will be called whenever the hex-view window wants some data to draw on the screen. Other uses for this function would be copying data to the clipboard, or saving a file to disk in blocks (by repeatedly calling it).

Because we only have access to a "small" section of the file at any one time, we have to be careful when we copy the file's contents into the destination buffer. The copy has to be split into sections if the requested amount of data would over-flow our view of the file. The render function handles this with a simple while-loop, as seen below.

?
BOOL sequence::render(BYTE *buffer, size_w offset, size_w length)
{
    while(length != 0)
    {
        size_w len = min(length, MEM_BLOCK_SIZE / 4);
        BYTE *source = get_adjusted_ptr(offset);
        if(source == 0)
            return FALSE;
        memcpy(buffer, source + offset, len);
        offset += len;  // advance the source position
        buffer += len;  // advance the destination offset
        length -= len;  // decrement the length of the copy
    }
    return TRUE;
}

That really sums up the whole design of the sequence document object. It may seem complicated at first, but there isn't any other way to do this sort of thing. We decided early on that we wanted access to 4Gb of file contents, so we just have to live with the consequences.

HexView Window Design

The design of the actual window that displays the hex-dump of a file is fairly straightforward. The only time the hex-view needs to access any of the file’s contents is when it needs to update its display. When it does this, it calls the sequence::render() method to obtain 1 line (16 bytes) worth of data to display.

I'm not going to describe how the hex-view window works because it would take another article to cover just that. The design is very straight-forward, and if you have a little Win32 knowledge then you shouldn't have any difficulty in understanding how the gui part works.

Don't forget I've included the full source-code to the hex-view application, and its pretty well commented. You've now have a good spring-board to develop this file viewer into a full-blown file editing application.

Coming soon – Part 2

The next article in the series, titled "Algorithms and Techniques" will cover the powerful span-table data structure, which I use in HexEdit to allow insertion and deletion from large sequences of data.

I will show you how to build on the existing sequence document class to provide this functionality by simply adding a few more member functions to the class. The source-code for the HexView window will remain unchanged – only the under-lying implementation of the sequence class will be modified.

See you next time!

James.

20 Aug 2001

Posted in Tutorials

win32
Powered by Drupal


Memory Techniques - Part 2 | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Memory Techniques - Part 2

Memory management techniques for large file editors

(Part 2 - Algorithms and Techniques)

So, you already know how to load and view a 4Gb file because you’ve just finished reading Part 1 of this article series. Now you know how manage large amounts of memory, we can move on and explore a very powerful technique for actually editing a large file.

Let’s have a quick recap of the original task at hand. We are developing a file editor capable of loading, viewing and editing a 4Gb file, which is the biggest file size currently supported by both Windows 9x and Windows NT/2000. I presented the source-code to the HexView mini-application, which is currently just a very simple file viewer with no editing capabilities. I also presented the sequence class, which represents a file, or sequence of bytes in an abstract manner.

The article will present an overview of the span-table data structure, which is used is the same data structure used by HexEdit to manage its files. The source-code supplied builds on the sequence class to use the span-table data structure. I'm not going to be getting down to any low-level coding, so you'll need to look at the sources if you want to see how its done.

Inserting and deleting data from a file

The best way to describe what this means is to use a diagram. The example below shows a file containing part of the well known saying "The quick brown fox jumped over the lazy dog". The file itself has the word "brown" removed from it. To illustrate the action of inserting text into a file, the word "brown " is inserted into the file to produce the proper saying.

Figure 1

By inserting or deleting data from a file, you can see that the file contents has to be shifted around to make room for the new data, or in the case of deletions, to overwrite the portion of the file that we want to delete. For now I’ll concentrate on inserting data into a file, because the same principles apply if we delete data as well.

Solution #1 – Quick to code but slow to execute

The easiest and most obvious method to insert data is to take a two-step approach:

  1. Shift along the file contents after the insertion point to make room for the inserted data
  2. Copy the data to insert into the space left by the shifted portion of file.

Whilst this seems like a perfectly good solution, the technique becomes seriously compromised when large amounts of data need to be shifted around. Think about inserting one character into a 4Gb file using a file editor – if the character is inserted at the start, the entire 4Gb needs to be shifted along to make room for this single insertion. This operation will take a very long time to complete, and is not an acceptable solution.

Solution #2 – The Span Table

The answer is to represent the large 4Gb file in some other way than a simple buffer or array. Whilst there are many different data structures which can be used to represent a file in an editor, there is only one sensible choice for our multi-gigabyte hex editor.

It is called a span-table (or piece-table). The name derives from the fact that a file is represented as a series of spans (or pieces) of data. These spans provide a level of indirection to the contents of the file. Whilst the file itself remains as a contiguous block of data, access to the file is always through the span-table. Instead of editing the file contents when an edit occurs, the span-table is modified to reflect the edit. The under-lying file contents will always remain unchanged, but because the span-table has been modified, it will reflect the contents in a different manner.

Figure 2

The diagram above illustrates an unmodified file, with the span table containing a single span which encompasses the whole file.

What is a span?

Glad you asked. A span is just a very simple data structure which contains enough information to represent a range of data in a file. The technical term for a span is probably a "descriptor" or something like that, but I prefer the name "span" because it is short and to the point. This is what a span would look like if you were using C or C++.

?
struct span
{
  unsigned int buffer;    /* identifies the buffer this span references */
  unsigned int offset;    /* start of the range of data this span covers */
  unsigned int length;    /* length of data */
  span *prev, *next;      /* linked-list */
};

Pretty simple, right? Figure 2 (above) illustrates a span with offset 0 and length 38, but does not show the value corresponding to the buffer. I have delibrately not shown the "buffer" id in the examples on this page because it is not really necessary for these simple examples (you can see for yourself what buffers are being used). Also, it keeps the diagrams simpler without the extra numbers flying around.

What is a span table?

A span table is any data structure used to collect a series of spans together in a sequential order. In my HexEdit application I use a doubly linked list to store the spans.

Apart from the actual span-list, there are two other important data structures used by our data sequence.

  1. The first is a read-only buffer containing the file's contents. This file-buffer never changes! The buffer is loaded once when the span-table is initialized. By using operating system support (in our case, memory-mapped-files) this load can be very quick, especially because we only need read-only access.The "filebuffer" will have a buffer-id of 0 (zero).
  2. The second is an append-only buffer which over time contains any inserted data. This buffer must obviously have write access, and must be capable of being resized. When the span-table is initialized, the "modifybuffer" is initally empty. It has a buffer-id of 1 (one).

There are obviously going to be other variables used by our span-table, but the three mentioned so far are the most important. Also, it doesn't really matter what id's you give to the file and modify buffers - as long as they are unique, you can use any scheme you want.

Inserting data into the span table

Now the fun begins. The first example will be the same as the original (inserting the text " brown" into the sequence at offset 9 - note the leading space). The diagram below shows the span-table after the text has been inserted.

Figure 3

The thing you must look at here is the spans in the span table. The original span which covered the whole file buffer has been replaced with three new spans. The first span refers to the data before the insertion point. The last span refers to the data after the insertion point. Both of these spans' lengths and offsets are set so that the spans cover the correct ranges of data. Also, both spans still have the same buffer-id.

The middle span references the inserted data (the word " brown"). This text has been appended into the modify-buffer. The middle span's offset and length are set to refer to just this string of text.

Hopefully you have appreciated what has happened so far. If you don't quite "get it" yet, then sit back and stare good and hard at the diagram until it sinks in!

Inserting even more data into the span table!

Our sequence now contains the text "The quick brown fox jumped over the lazy dog".

The next example shows the span-table after the text "est " has been inserted (at index 9 again). Please note that this index does not refer to an index the original file, but to the logical position in the sequence.

{short description of image}
Figure 4

This time you will see that the only one new span has been inserted into the span-table. The insert occured at a position in the sequence which was at a span boundary. In this case, a single span was inserted with the text "est". The inserted text has also been appended to the modify-buffer. Our sequence now holds the text phrase "The quickest brown fox jumped over the lazy dog".

What does this mean?

It means we can insert any amount of data to any position in a file! This insertion process can be divided into three separate cases. The main difference occurs when data is inserted at a position in the sequence which is either in the middle of a span, or in-between two spans (on a "span boundary").

  1. Inserting data in the middle of a span.
    In this case, the span must be replaced with three new spans - spans which represent the data before the insertion point, after the insertion point, and actual the inserted data itself.
  2. Inserting data at a span boundary.
    When text is to be inserted in this case, only one new span is required (to represent the inserted text). This new span can be simply linked in-between the two spans which reside either side of the insertion point (or index).
  3. Inserting immediately after a previous insertion.
    This is a special case. No new spans are created. Instead, the previous inserted span's length is adjusted to include the new data, and this data is simply appended to the end of the modify-buffer. This saves memory because no new spans are created, and also keeps the span-table as simple as possible.

Deleting data from the span table

The next logical operation to cover is the act of deleting data from the sequence. As always, a picture speaks a thousand words, so here's a diagram. In this example, the data from offset 9 to 29 (20 bytes) has been removed from the original sequence, to produce the phrase "The quick lazy dog".

Figure 5

What has happened here is that the single span covering the original file has been split into two new spans, the first to represent the data before the deletion, and the second to represent the data after the deletion. Hopefully you can use your imagination to realise the further possibilities of span manipulation.

This data removal can be divided into three separate cases.

  1. Deleting data from the middle of a span.
    This is the case described in the above diagram. In this case, the span is replaced with two new spans, the first to represent the data before the deletion, the second for the data after the deletion.
  2. Deleting on a span boundary (at the start or end of a span).
    In this case, the existing span's offset (when deleting at the start of a span) or length (when deleting at the end) can be simply modified to reflect the change.
  3. Deleting data which is covered by several spans.
    This is the worst case, and requires careful coding. In this case, several spans may need to be removed from the list to represent the new sequence of data.

Replacing data (over-writing)

Data replacement is a hybrid form of insertion and deletion. I won't describe the process here, and I advise anyone not to attempt this operation until they have a fully functional delete operation.

The fundamental steps are the same as those of the delete (or removal) operation. Existing spans in the table must be removed to make way for the new data. Instead of simply linking the two halves of the sequence back together after the deletion (as happens with the delete method), a new span is inserted into the table to represent the new over-writing data. This is where the similarity of the insert operation comes into play.

Multi-level Undo and Redo

I won't describe in detail how to achieve this, but just give a very simple overview instead.

I have been talking throughout this discussion on replacing spans with new spans, and so on. What I didn't talk about was what to do with these spans once they have been removed from the span-table. The obvious thing to do is just to delete them (i.e. with the C++ delete[] operator), but there is a much better solution.

Any span that is removed during a span-table "edit" is added to a stack (of spans!). These spans still have their linked-list pointers (*prev, *next) left untouched. So, the spans on the undo-stack still point to valid spans in the span-table, even though they aren't in the table themselves.

When the time comes to undo the last edit, we perform two steps. The first is to pop off the spans from the undo-stack, and look at their linked-list pointers. This tells us exactly where in the span-table to put these spans back into! The second thing to do is to take the "newer" spans in the table that our undo-spans replace, and push them onto the redo-stack! Can you see where this is going...?

The concept is simple, and the implementation is not much more difficult. Just look in the sources to see it in action!

Rendering the span-table

It's all well and good inserting and deleting data from our sequence, but it doesn't mean a thing unless we have some way of actually viewing the sequence contents.

This is actually a very simple process. All we do is a linked-list traversal, visiting each span in turn, and retrieving the region of data from the buffer that the span represents. This is what your code might look like:

?
span *sptr = spanhead;  // spanhead points to the first span in the list
while(sptr != 0)
{
  char buf[100];
  // get a pointer to the memory that this span refers to.
  char *mem = get_adjusted_ptr(sptr->buffer, sptr->index);
  // copy the data that this span represents into our local buffer.
  memcpy(buf, &mem[sptr->index], sptr->length);
  //null-terminate.
  buf[sptr->length] = '\0';

  //print it out!
  printf(buf);
  //goto the next span!
  sptr = sptr->next;
}

Remember the get_adjusted_ptr function from the previous article? This is where it pays off - you can see how simple the above loop is. The beauty of the design of the span-table is that it doesn't matter which buffer / file a span refers to - at the end of the day, it is just a block of memory, and the get_adjusted_ptr function performs the hard work of retrieving that memory for us.

Conclusion

As you can see, the span-table is a very flexible method to implement modifications to a file, or sequence of data. Because edits to the span table can be achieved in constant time, it does not matter how big the under-lying file is - inserting data into a 1Kb file takes the same time as inserting into a 1Gb file.

There are disadvantages to using the span-table method though, but these are managable.

The first problem comes when it is time to save the sequence back to disk. The entire sequence must be rendered into a temporary file, which is then copied back over the original file (and the sequence re-initialized with this new file, and the span-table reset also). We can cope with this though. This is a one-time operation, and all the memory copying only occurs this once, instead of every time a modification occurs as is the case with other file handling methods.

The second problem is indexing a particular byte in the sequence. Because we are using a linked list of spans, it is not possible to jump directly to a particular position in the span-table. Therefore to locate a specific span, we need to traverse the span-table from the start each time. This is fine when there haven't been many edits to the table (and the list is short), but could become time consuming if there are many thousands of spans in the table.

This is not as bad as it seems though. We can operate some form of span caching, to reduce the number of span traversals needed to locate a particular span. We can also optimize inserts and deletes (described in their appropriate sections), so that when appropriate we can concatenate inserts and deletes into a single operation, thus reducing the span-count in the table. The last thing to bear in mind is that it isn't very likely that someone will make thousands of seperate modifications to a file in our hex-editor without periodically saving the sequence, so in reality these negative points don't come into effect. Just download the HexEdit application from this site and see for yourself how fast this span-table method is!

Full source available!

You may have noticed the size of the download at the top of this page. This is the full source-code to the HexView library I use in my HexEdit application. That's right, the full source. Included is a complete implemention of the span-table, encapsulated by the sequence class. This library comes complete with absolutely NO SUPPORT what-so-ever! Unless you have any comments or suggestions for improvement, then I probably won't be able to help you out unless you have a very simple question - I really don't have the time (or the inclination) to support something like this. Afterall, I'm doing this for free, and in my spare time.

Coming soon – Part 3

I havn't decided what the next article in this series is going to be yet. If you have a particular aspect of the span-table you'd like me to cover, then feel free to email me

Have fun! James.

30 Aug 2001

Posted in Tutorials

win32
Powered by Drupal


Self-deleting Executables | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
selfdel.zip

Published on 22 Jan 2012

7.64 Kb

Download

Self-deleting Executables

Techniques which allow an executable file to delete itself whilst running

This is a subject that tends to come up every so often in the newsgroups, so I thought I'd write an article about the techniques I've collected to enable an executable to delete itself from disk (whilst running, that is). There is very little information on the web, and what information exists is also hard to find.

Why would you want a program to delete itself? The only good reason I know of is an un-install program that needs to remove an application, as well as itself in order to completely remove the application from disk. I'm sure there are other good reasons for self-deleting executables, but un-installation is probably the most common.

I know many different methods, each of which I will describe shortly. I must take this opportunity to mention that only one of these techniques has been developed by myself. Apart from the last method, I am presenting the same material described by Jeffrey Richter in his January 1996 MSJ column, titled "Win32 Q&A". Click here to read the original article. The rest of the techniques were originally developed by Gary Nebbett - or heavily influenced by his work. So, I hope no-one thinks I am ripping off other people's ideas because all these techniques have existed for years before I encountered them.

There used to be two methods for a program to delete itself - actually doing it from the same program, and forcing a separate program to do it on your behalf. When Windows XP came out the "self-deleting" executable became a part of history - it is no longer possible for a Windows program (which runs on any version of Windows) to delete itself. However there are lots of techniques which can achieve the same effect - the ones I know about are listed below.

Why the obvious doesn't work

If you try to run the following code, nothing will happen.

?
TCHAR szFilePath[_MAX_PATH];
// Get current executable path
GetModuleFileName(NULL, szFilePath, _MAX_PATH);
// Delete specified file
DeleteFile(szFilePath);

The code above retrieves the full path to the current executable, and then tries to delete the file whilst running. This code will fail - because all 32bit versions of Windows (95, 98, ME, NT, 2000, XP etc) use a mechanism called memory-mapped files to load an executable image into memory.

When the Windows loader runs an executable, it opens the executable's disk file and maps that region of disk into memory, effectively loading the executable into memory. This disk file is kept open during the lifetime of the process, and is only closed when the process terminates. Because of this lock on the file, it is normally impossible to delete an executable file whilst it is running. Just run notepad.exe, then try to delete the notepad executable - it won't work.

The MoveFileEx method

I'm going to mention this technique even though it doesn't really solve our problem, because it is quite useful to know and can be handy in other situations.

?
MoveFileEx(szExistingFile, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);

The MoveFileEx API moves a file to a new location. When you pass NULL as the second parameter, this causes the file to be moved "nowhere", effectively deleting the file. Now, ordinarily this would fail if you tried this with the path to the current executable. However, if we specify MOVEFILE_DELAY_UNTIL_REBOOT in the dwFlags parameter, this tells Windows not to move (or delete) the file until the system is shutdown or rebooted.

There are a few problems with this technique. Firstly, you cannot remove the directory that the executable resides in. Second, the file is not deleted immediately - if your system doesn't get rebooted very often, then the file will stay around. But the biggest problem is that MoveFileEx is not implemented on Windows 95/98/ME. Today this is not really a problem and the MoveFileEx is the neatest, safest way for a program to delete itself.

The WININIT.INI method

Under Windows 95/98/ME, an application called WININIT.EXE runs each time the system is started. This application looks for a file called WININIT.INI. If this file exists, WININIT.EXE looks for a section called [Rename]. Each entry in the [Rename] section specifies a file rename operation which will occur (once) when time the system starts. This method is obviously very similar to the MoveFileEx method described above.

?
[Rename]
NUL=c:\dir\myapp.exe
c:\dir\new.exe=c:\dir\old.exe

The filename to the left of the equal sign specifies the new name of the filename on the right. When NUL is used as the new filename, the file is deleted. This means that an application can write an entry into WININIT.INI, specifying NUL and the applications own full path.

You must be careful when writing an entry to the [Rename] section. You cannot use WritePrivateProfileString API call, because this function prevents any duplicate entries from occuring under the same section. This restriction would prevent there from being more than one "NUL=" entry. Therefore you must manually write any entry if you want to use this technique.

The Self-Deleting Batch File method

This is quite a well known method, and was documented in MSDN some time ago. This technique works on both Windows 95 and Windows NT. It works because MS-DOS batch files are able to delete themselves. To test this technique, create a small batch file containing the single command:

?
del %0.bat

The batch file, when run, deletes itself and issues an error "The batch file cannot be found". This error is just a simple message, so it can be safely ignored. By itself this isn't too useful, but when modified to delete our executable it solves our problem, albeit in a rather forceful manner. Our executable will create a batch file (called C:\DelUs.bat) with the following content:

?
:Repeat
del "C:\MYDIR\MYPROG.EXE"
if exist "MYPROG.EXE" goto Repeat
rmdir "C:\MYDIR"
del "\DelUS.bat"

This batch file repeatedly attempts to delete the specified file, and will run continuously consuming CPU until it succeeds. When the execuable has been deleted, the batch file then deletes itself.

The executable needs to spawn off the batch file using CreateProcess, and then should exit immediately. It would be a good idea to give the batch file's thread of execution a low priority so that it doesn't get much execution time until the original executable has terminated.

The sourcecode download at the top of this article contains the full code to this technique.

The COMSPEC method

This is a method kindly shared by Tony Varnas, who recently emailed me this snippet:

?
BOOL SelfDelete()
{
  TCHAR szFile[MAX_PATH], szCmd[MAX_PATH];
  if((GetModuleFileName(0,szFile,MAX_PATH)!=0) &&
     (GetShortPathName(szFile,szFile,MAX_PATH)!=0))
  {
    lstrcpy(szCmd,"/c del ");
    lstrcat(szCmd,szFile);
    lstrcat(szCmd," >> NUL");
    if((GetEnvironmentVariable("ComSpec",szFile,MAX_PATH)!=0) &&
       ((INT)ShellExecute(0,0,szFile,szCmd,0,SW_HIDE)>32))
       return TRUE;
  }
  return FALSE;
}

This method is very similar to the batch-file method (above) but is alot neater in its implementation. It works under all 32bit versions of Windows (95,98,ME,NT,2000,XP), as long as the COMSPEC environment variable is defined. This is always defined (by default) to be the full path to the operating system's command interpreter. For Windows 95, this is "command.exe". For Windows NT, this is "cmd.exe".

The function will only work if the executable has exited, so it is important to call this function and then exit immediately. It works by spawning a copy of the system's command interpreter, and asking it to perform the following command:

?
del exepath >> NUL

This deletes the current executable, and pipes the output to NUL (no output). The shell process is created with a hidden window as well, so the whole process is invisible.

The DELETE_ON_CLOSE method

The CreateFile API call accepts several flags which affect how a file is created or opened. One of these flags, FILE_FLAG_DELETE_ON_CLOSE, specifies that the file will be deleted when the last handle to it is closed. The basis to this technique will be to run an executable with this flag set, so that when it exits, it is deleted automatically.

The first step is to create an empty file with the DELETE_ON_CLOSE flag specified. The exact binary content of the current executable file is then copied into this new file, in effect duplicating the executable on disk. A new process is then created (using the new executable file). This has the effect that the duplicate file's handle count is incremented. Also, when the new process was created, the full path of the current process was passed through the command-line argument.

Next, the current executable (which wants to delete itself) closes the file handle used to create the new process, and then exits. Now, the duplicate's file-handle count is decremented, but because CreateProcess incremented its handle count when it started, the file is not deleted.

At this point, the duplicate executable has started running. The PID specified on the command-line is used to open a handle to the original process. The duplicate waits for the original process to terminate, using WaitForSingleObject. When this call returns, the duplicate can call DeleteFile on the filename also specified through its command-line argument. The original executable (the one that wanted to delete itself) has been successfully deleted. This just leaves the duplicate copy, which exits normally. The duplicate's file-handle count drops to zero, the DELETE_ON_CLOSE flag comes into effect, and the duplicate file is deleted also.

It sounds a bit complicated, but it's not too difficult. Here's the steps one more time:

?
[ Current process ]
1. Create a new file with FILE_FLAG_DELETE_ON_CLOSE.
2. Copy the current executable's content into the new file.
3. Create a new process with the duplicate executable:
4. Pass the current executable's full path and PID in the call to CreateFile.
5. Sleep for a short time to give the new process time to start.
6. Close the new file.
7. Exit current process.
[ Duplicate process ]
8.  Wait for the process specified on command-line to die.
9.  Delete file specified on command-line.
10. Exit duplicate process.

There are just a couple of technicalities to mention. First, when the "new" process is spawned, the "old" process must sleep for a short period, enough to let the Windows loader open the file and create the process (thus incrementing it's file count).

Second, the new process must wait until the old process terminates, which releases its file count.

Third, when the duplicate executable is created, it must also have the FILE_SHARE_DELETE flag specified, otherwise CreateProcess will fail, because it won't be able to open the file whilst we have it open with the DELETE_ON_CLOSE flag set.

Obviously this method will require careful coding, because the program must be written in such a way so that it can perform these dual tasks. The "new" executable must know that it's job is to delete the file specified on the command line, for instance.

It's a little messy, but it does work very well. In fact, the uninstall program that I wrote, which is included with the software you can download from this site, uses this very method. I've included an example program which demonstrates this technique.

An alternative method is to write a very small stand-alone executable, which it's sole task is to delete the file-name specified on it's command-line. This executable could then be imbedded as a "payload" to the executable which wants to delete itself. This payload would be created and executed in the same way as described above.

The Ultimate Self-Deleting Executable!

This inline assembly snippet is short and simple. I can't claim credit for this code - I found it posted on usenet some time ago. The author's name is Gary Nebbett, author of "Windows NT Native API Reference". Actually this technique was first published in Windows Developer Journal around 1996.

?
#include <windows.h>
int main(int argc, char *argv[])
{
    char buf[MAX_PATH];
    HMODULE module;
    module = GetModuleHandle(0);
    GetModuleFileName(module, buf, MAX_PATH);
    CloseHandle((HANDLE)4);
    __asm
    {
      lea     eax, buf
      push    0
      push    0
      push    eax
      push    ExitProcess
      push    module
      push    DeleteFile
      push    UnmapViewOfFile
      ret
    }
    return 0;
}

This snippet ONLY works under Windows NT and 2000. As soon as you compile and run the above program, it just disappears from disk! It works because under NT and 2000 the OS keeps a usermode HANDLE reference to the memory-mapped file which backs the executable image on disk. This HANDLE always has a value of "4" under these OSs.

Unfortunately this gem only works under NT/2000, not XP or .NET Server. This is because these OSs no longer maintain a usermode HANDLE to the executable's memory-mapped file - this section object is only accessible in kernel using a pointer reference. So although this technique is incredibly slick (it is the only true self-deleting method) it is no longer as-is.

Now for Windows 9x!

Thanks must go to Tony Varnas again for some great detective work. He managed to unearth the following assembler snippet which works exactly like the snippet above, but this time for Windows 95,98,ME (tested on all three).

?
#include <windows.h>
int main(int argc, char *argv[])
{
    char buf[MAX_PATH];
    HMODULE module;
    module = GetModuleHandle(0);
    GetModuleFileName(module, buf, MAX_PATH);
    __asm
    {
      lea     eax, buf
      push    0
      push    0
      push    eax
      push    ExitProcess
      push    module
      push    DeleteFile
      push    FreeLibrary
      ret
    }
    return 0;
}

Solution for XP+

After the original self-deleting technique Gary Nebbett posted a solution on Jan 19 2002 which works for XP. The following code should be packaged as a DLL which is callable from RunDLL32. The idea is that an external program (RunDLL32) is used which first waits for the original executable to finish. The DLL which is loaded into RunDLL then performs the original self-deleting trick. This is possible because the kernel maintains references to DLLs differently to the main executable.

?
#include <windows.h>
#pragma comment(linker, "-export:CleanupA=_CleanupA@16")
extern "C" void CALLBACK CleanupA(HWND, HINSTANCE, PSTR, int)
{
    static MEMORY_BASIC_INFORMATION mbi;
    VirtualQuery(&mbi, &mbi, sizeof mbi);

    PVOID module = mbi.AllocationBase;
    CHAR buf[MAX_PATH];
    GetModuleFileName(HMODULE(module), buf, sizeof buf);
     __asm
    {
        lea     eax, buf
        push    0
        push    0
        push    eax
        push    ExitProcess
        push    module
        push    DeleteFile
        push    FreeLibrary
        ret
    }
}

The idea that an external program could be used to self-delete it's parent is what influenced me to develop the following technique which works for all versions of Windows, but does not require a separate DLL which is dropped to disk.

The Definitive Self Deleting Executable

I am pleased to present what I believe is the definitive self-deleting executable, for all versions of Windows.

Update: Ranju. V. has taken this method even further. See his article for more details:http://blogorama.nerdworks.in/comment.aspx?entryID=21

Now this technique is complicated so I won't include the code here - please get it from the sourcecode zipfile (selfdel05.c). Instead I will describe how it works and the issues involved.

  • Firstly a child process is created in a suspended state (any process will do - i.e. explorer.exe).
  • Some code is then injected into the address-space of the child process.
  • The injected code waits for the parent-process to exit.
  • The parent-process is then deleted.
  • The injected code then calls ExitProcess, which terminates the child process.

The neat thing about this technique is that the spawned child process never really executes (no windows ever appear) - only the "self-deleting" code in the child gets to execute. After the parent process terminates and is deleted, the child process terminates using ExitProcess, preventing anything further from happening. Effectively an arbitrary child-process is hijacked and coerced into deleting it's parent.

My first version of this technique (inside selfdelxp.c) used CreateRemoteThread to inject the code into the suspended child process. This was quite neat because the child never ran - only the injected thread with the self-deleting code ever executed. The downside to this technique is that it only ran on Windows-NT based platforms.

The latest version (selfdel05.c) does not use CreateRemoteThread to inject code into the child. It therefore works on all versions of Windows. The self-deleting code is injected into the spawned child by hijacking the primary thread of the process. The thread's stack is manipulated in such a way so that space is "allocated" on the stack - into which the self-deleting code is written. The instruction-pointer for the thread is then altered so that it points to the injected code on the thread's stack. When the child process (and primary thread) is resumed the code executes right off the stack, deleting the parent process, and then exits.

There are issues involved with this last technique. The first is the injection of code into an already running thread - this is not a desirable thing to do, but because the thread's "real" execution is never resumed (the process exits as soon as the parent is deleted) it seems like a viable way to do this.

The second issue involves the Win32 environment. When a child process is created in a suspended state the program has not started executing yet - there is still alot of Win32 environment setup that must be performed before the entry-point to the executable is called. So when we hijack this thread to do our bidding, we are doing so in an environment that is not yet fully initialized, and is not ready for Win32 API calls (it is still mid-way through executing some internel part of the OS!). The API calls that are made (WaitForSingleObject, DeleteFile etc) do however work - but this is not guaranteed in furture OS releases.

The exact same issue exists when you CreateRemoteThread into a process that was started as suspended - the OS hasn't yet finished intializing your process's environment before the remote thread starts to execute. This is why alot of Win32 API calls will fail (such as RemoveDirectory).

Conclusion

I've used this article to describe all the methods I know of to delete an executable whilst it is running. You can take your pick of the techniques, but the last method presented is currently the only one which works well for all versions of Windows.

1 Oct 2001

Posted in Tutorials

win32
Powered by Drupal


Reducing Executable Size | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
AggressiveOptimize.h

Published on 22 Jan 2012

2.9 Kb

Download

Reducing Executable Size

A tutorial covering a variety of techniques for creating tiny executables.

There are so many large and bloated applications around today that most people just assume that this is normal and quite willingly put up with poor performance and having to buy larger harddrives.

In this tutorial I will present all of the techniques I know (and use) to reduce the size of executables. Some of these techniques can be applied to any C / C++ Win32 project, whilst other techniques are quite restrictive as to the type of application you can apply them to. I regularly use the techniques on this page to build tiny executables - less than 1Kb sometimes!

The techniques described here concentrate on reducing the file size of an executable. They only partly reduce the amount of memory (RAM) required by a program. If your program uses alot of memory to load bitmaps or animations, then this is a completely different issue which can only be controlled by you, the programmer.

Dependence on third-party libraries

If you are using MFC then you know what I'm talking about. MFC (and many other frameworks or libraries) by their very nature introduce code bloat. This is because libraries tend to be general purpose. So, even though you use only a small fraction of what a library offers, you are stuck with using the whole library. If you really want to build a small executable file then you have to bite the bullet and stay away from such frameworks. You have to have complete control over what gets compiled into your executable, and MFC doesn't give you that control. Jump to the section on Compiler and Linker Settings to try to minimize the damage your MFC project is doing to your harddisk!

Debug Vs Release build

It constantly amazes me the number people that don't understand the difference between a debug and release build of their application. Always distribute the release version of your program. A debug build has alot of bloat in it which, whilst assisting the debugger to perform its job properly, doesn't do you any favours if you are trying to keep your program small. A release build on the other hand has minimal bloat and is optimized which always reduces code size.

If you really want to include debugging information for your release builds, then configure Visual Studio's linker options to use an external debugging database which the exe links to.

Static Vs Dynamic linking of library files

One of the first things you could do to reduce executable size is to use the DLL version of your standard libaries. This may be the MFC DLL (mfc42.dll) or the DLL version of the C run-time (MSVCRT.DLL). In either case, you will end up with a smaller executable. There are disadvantages to this approach though. You may still need to distribute the runtime DLL at some point (for example, Win9x doesn't include MSVCRT.DLL),

I used to say that it best to statically link to your runtime library - however, with Windows 95/98/ME pretty much dead, there is little reason not to link against MSVCRT.DLL. You're executable will be very small and you get all the benefits of using the entire C-runtime.

The C-runtime and default libraries

The C-runtime is a collection of functions and services that you probably didn't even know existed. The run-time performs many tasks which are required by a C or C++ implementation.

You may have noticed that the smallest C program you can write in visual studio is usually around 20-30Kb. Even the tiny "Hello, world" program results in a not so tiny executable size.

?
#include <stdio.h>
int main(void)
{
    printf("hello, world\n");
    return 0;
}

This is not a fault of the compiler or linker. It is only doing what you are telling it to do. A normal, standard C program gets it own copy of the C runtime. Unless you tell the linker otherwise, this is what you will always get. The following program illustrates not only the above "hello world" exacmple, but also a very simplified outline of what happens during program startup:

?
//
// This is the same "hello world" as above
//
#include <stdio.h>
int main(void)
{
    printf("hello, world\n");
    return 0;
}
//
// This is what you never get to see...it is the proper
// entry-point into this application.
//
int mainCRTStartup()
{
    int retval;

    init_heap();
    parse_command_line();
    init_global_vars();
    init_exception_handling();
    // finally call the user-defined main
    retval = main();
    // terminate all threads and exit
    ExitProcess(retval);
}

If we wrote our own minimal version of the C runtime, then we can remove this extra code and rely on the operating system to do much of the work for us. In a moment I will tell you how to remove the default libraries from your program, but there are a few rules you need to obey if you are to do this.

The following list contains some of the tasks that go on behind the scenes in a C or C++ program: You need to be aware of these tasks, and how to write software that doesn't rely on the features they provide.

Heap Management

Every time you call malloc, new, free or delete, the heap manager is behind the scenes reserving memory from the operating system and trying to keep memory fragmentation down. The heap manager is really just a collection of functions which get called by malloc, free etc. You don't NEED the heap manager though. If you replace the heap manager with small functions which call Win32 memory functions, then you can save alot of space in your program. You lose a bit of performance, but there is always a tradeoff in anything you do.

Startup code

If you didn't know already, it is very uncommon for main to be the first function called during the lifetime of your executable. Under Visual C, the standard entry-point into a windows application is called WinMainCRTStartup, whilst a console application starts life in the mainCRTStartup function.

These entry-point functions perform many tasks before your main function is called. Initializing the Heap Manager. Retrieving the command line arguments and putting them into the argv[] array. Calling your main function. The final job of the startup function is to call ExitProcess. Without this, Windows will carry on executing random code at the end of your program, which will probably crash your computer.

Math routines

Everytime you use certain floating point operations, or 64bit integer arithmetic, then an appropriate CRT function will be automatically included to do the actual arithmetic, if the requested operation does not exist as part of the Intel instruction set. This could be something as simple as an integer division (i.e. int a=b / 5 ) - if you look at the assembler that the compiler generates for some operations like these, you can clearly see the resulting function calls.

Standard library calls

Whenever you call printf, atoi, strcpy or any other C library function, then that function is linked into your executable. Some functions are small (strcpy, atoi etc), but complicated functions like printf will always add a few Kb to your program. If you can find other ways to perform these tasks then you can reduce the amount of extra code that is linked into your application. There are many Win32 equivalents of the standard C library calls which you can use instead.

Static object initialization

Any variable or structure defined at global scope (i.e. outside of any function) needs to be initialized at program startup, so that when your functions go to use these variables, you know they contain the right values. These variables have to be initialized somehow, and the linker adds the necessary C runtime code to do this for you.

C++ exeception handling

God only knows how much extra code is included when you use exception handling. If you use this feature then you'll have your work cut trying to reduce your executable size. If you want small programs, then turn off exception handling.

If you have decided to remove the C runtime from your applications, then you need to be very careful when you write your code. You can no longer rely on static object initialization. The C library calls need to be used with care, because many library functions have to be initialized correctly in the standard startup code. You cannot use new, delete, malloc or new unless you actually write these yourself.

Use Win32 equivalents

There are several standard-C functions which have direct replacements in the Windows API. Because these API calls are inside the operating system, this frees up space in your executable that might have been used otherwise. Using the win32 functions instead will obviously lead to unportablity, so you need to decide how badly you want to reduce your exe size. Here is a list of C functions, and the eqivalent win32 function.

Standard function | Win32 equivalent
malloc | HeapAlloc
free | HeapFree
strcpy | lstrcpy
strcat | lstrcat
strncpy | lstrncpy
strncat | lstrncat
strlen | lstrlen
strcmp | lstrcmp
strcmpi | lstrcmpi
memcpy | CopyMemory
memset | FillMemory or ZeroMemory
memmove | MoveMemory
toupper | CharUpper
tolower | CharLower
isalpha | IsCharAlpha
isalnum | IsCharAlphaNumeric
islower | IsCharLower
isupper | IsCharUpper
sprintf | wsprintf
vsprintf | wvsprint

There is also a comprehensive list available on the Microsoft site which lists even more functions:

http://support.microsoft.com/default.aspx?scid=kb;EN-US;q99456

There is a small group of functions that are always available to you, even if you remove the default libraries. These are called intrinsic functions. These instrinsic functions might not be the same versions as those found in the standard libraries, and might not be as optimized. If you want to use these functions, it is always best to include the relevant headers.

  • memcmp, memcpy, memset
  • strcmp, strcpy, strlen, strcat, strset

Removing the C run-time library (RTL)

This is pretty straight forward. Just click on the "Ignore all default libraries" in the Link tab in your project settings, or use the "/NODEFAULTLIB" linker setting. However, you probably wont be able to compile your project any more.

Visual C++ requires that you provide three functions (for a normal C program): __purecall, new and delete. A C program requires malloc and free. Obviously, if you don't do ANY memory allocation then you don't need them. Here are minimal implementations of these functions:

?
void * __cdecl operator new(unsigned int bytes)
{
  return HeapAlloc(GetProcessHeap(), 0, bytes);
}
void __cdecl operator delete(void *ptr)
{
  if(ptr) HeapFree(GetProcessHeap(), 0, ptr);
}
extern "C" int __cdecl __purecall(void)
{
  return 0;
}

This just leaves the entry-point functions. Define which ever one is required by the type of executable you are building. At the very least, your entry-point function must call either main WinMain or DllMain.

?
int __cdecl    mainCRTStartup();
int __cdecl    WinMainCRTStartup();
BOOL __stdcall _DllMainCRTStartup(HINSTANCE, DWORD, LPVOID);

More information in MSDN

There are two important articles which you should read if you want to understand more. I strongly suggest you read the October 1996 "Under the Hood" article, written by Matt Pietrek in the Microsoft Systems Journal. This article was updated recently and published in the Janurary 2001 "Under the Hood" column of MSDN magazine. Go here to read this article online.

The second article you should read is called "Remove Fatty Deposits from Your Applications Using Our 32-Bit Liposuction Tools", also published in the October 1996 MSJ magazine. Go here to read this article online.

Goto Matt's homepage here.

Once you have read these articles, then you don't need to do anything other than download Matt's LIBCTINY and link it into your application. You don't need to alter your link settings at all. This cool little library replaces the default C run time and gives you tiny programs with no effort at all. Most of the programs on this site where compiled and linked using this library. The sample program on this page includes LIBCTINY and shows you how to create a small program.

Use the right Compiler settings

If you don't want to remove the C run-time, or you want to go even smaller, then read on. Careful use of compiler and linker settings can have a dramatic effect on the size of your application.

As long as you select the Release build for your project then you are on the right track. However, there are a few important options that you should make sure are set for all your source files.

Switch | State | Description
/Og | on | Global optimizations.
/Os | on | Favour small code.
/Oy | on | No frame pointers.
/Zl | on | Prevents the compiler from inserting a "defaultlib" reference to each object file, which causes the C runtime to be implicitly linked.
/Gy | on | Enables function-level linking. Prevents unused functions from being included.
/GX | off | Enables C++ exception handling. You really don't want this set if you are aiming for small executables.
/GZ | off | Enable run-time checks for call stack validation

You can turn on the required optimizations by using a #pragma statement at the top of your source files.

?
#pragma optimize("gsy", on)

Use the right Linker settings

There are several aspects of an executable file's structure that can be adjusted with the linker settings. The linker that ships with Visual C++ is very powerful, and with the right options you can get some amazingly small programs out of it.

Switch | State | Description
/FILEALIGN:number | on | Undocumented (VC 6 only). Specified the alignment of each section in the executable, as it is stored on disk. number is in bytes, and must be a power of 2. The default is 4096, but a smaller number results in a smaller executable because there will be less padding between the sections. Whilst very small numbers can be used here, an executable sometimes won't load correctly with a section alignment of less than the "safe" 512.
/ALIGN:number | N/A | Specifies the alignment of each section in the executable, when it is mapped into memory. Has no relation to file-alignment (above), but an apparent bug in visual studio causes this option to also effect file-section alignment, as well as the intended image-section alignment, under certain circumstances. Default is 4096, it is best to leave this value alone.
/NODEFAULTLIB | on | Prevents the linker from using the standard C library.
/ENTRY:function | on | Tells the linker the name of the function to be used as the executable entry-point.
/OPT:NOWIN98 | on | Undocumented. Essentially the same as the FILEALIGN option. Reverts to 512 byte file alignment, instead of the default 4096 byte alignment. This setting causes slightly slower load times, but smaller executables.
/MERGE:from=to | on | This option combines the first section with the second section, often reducing exe size. Be careful with section read/write attributes.
/OPT:REF | on | Prevents functions and data that is never used from being included.
/INCREMENTAL | off | This setting enables incremental linking. Whilst this speeds up the build time of your program, it increases exectuable size.
/FORCE:MULTIPLE | ?? | Sometimes useful when you are messing with removing the C startup code.

A quick word about the /FILEALIGN and /ALIGN linker options. Playing with these settings individually is often good enough. You can reduce executable size quite considerably by setting /FILEALIGN:512. However, some magic can be achieved by setting both /ALIGN and /FILEALIGN to the same value. (Note that setting /ALIGN through the Project->Settings dialogbox erronously effects the /FILEALIGN setting also). (I'll discuss this later on).

These linker settings can be specified using the standard project-settings dialog, or (more helpfully) by the following #pragmas.

?
// Set section alignment to be nice and small
#pragma comment(linker, "/FILEALIGN:0x200")
// Merge all default sections into the .text (code) section.
#pragma comment(linker,"/merge:.rdata=.data")
#pragma comment(linker,"/merge:.text=.data")
#pragma comment(linker,"/merge:.reloc=.data")

Placing just these lines into one of your project's file will result in considerably smaller programs. Use this handy header file which you can #include in every project.

Control your Stack

It is a little known fact that Visual C++ automatically inserts what are known as "Stack Probes" into your executable if it detects (at compile time) that your program stack is going to be over 1 page-size in length (4Kb).

If you store too much data (i.e. buffers / arrays) as local variables in your functions, then this will often result in stack-probes being inserted. It's not a big deal - the code is there to make sure that there is enough memory committed to the stack during runtime. However, it still adds a small amount of code (the _chkstk function, for example), so being careful with the amount of local-data in your functions can help to eliminate these probes.

Use an Executable Packer

An executable packer or compressor is a tools which squeezes a program down as small as possible. Basically, they work by compressing an executable file, which is added as a "payload" to a very small decompressor stub. When you run a packed executable, you are really running a small decompressor program. When executed, the stub decompresses the attached (compressed) executable and runs that. No other external decompressing programs are required.

The disadvantage of this technique is that multiple copies of your program will not share the same memory location, so more RAM is required when running multiple instances. For small programs though an executable packer can work wonders - sometimes reducing exe size by as much as 50%.

The best packer I have found is called UPX (Ultimate Packer for eXecutables). It is fast, compresses very well, and is also FREE. So what are you waiting for? Go to http://upx.sourceforge.net and get your copy now!

The Smallest Win32 Executable

OK, let's put together everything in this tutorial and see how small we can get an executable to be. The following single C program compiles down to a tiny 480 bytes using Visual C++ 6.0!!

?
#include <windows.h>
// Make section alignment really small
#pragma comment(linker, "/FILEALIGN:16")
#pragma comment(linker, "/ALIGN:16")// Merge sections
#pragma comment(linker, "/MERGE:.rdata=.data")
#pragma comment(linker, "/MERGE:.text=.data")
#pragma comment(linker, "/MERGE:.reloc=.data")
// Favour small code
#pragma optimize("gsy", on)
// Single entrypoint
int WinMainCRTStartup()
{
   return 0;
}

That's a pretty small program! OK, it doesn't do a great deal, and it also only executes under NT/2K, but it just goes to show that with a little understanding of how a C program works, together with how your compiler and linker generate your application binary, you can get some pretty impressive results. I realise that it is possible to shave maybe 100 bytes further from this program - but you will need to resort to assembly language and a Hex Editor to be able to beat this all-C solution.

How does it work? The #pragma settings do alot of the work, but the basic reason that this executable is so small is that there is no c-runtime included. Why? Not because of the "no-default-libraries" linker switch, but because WinMainCRTStartup has been explicitly defined. When this program is compiled, the linker has to locate the default entry-point function (defined with the /ENTRY linker switch). By default this is WinMainCRTStartup. The linker finds this function in the the compiled object file for the program, before it looks in the import libraries for the project. Because our version WinMainCRTStartup doesn't call any of the C-runtime startup routines, these functions never get referenced, and the linker doesn't include them - it's as simple as that.

Conclusion

I have presented a few techniques that you can use to keep your executables small. This subject can be pretty complicated, especially when you start to use some of the more powerful compiler and linker settings. However, you only need to perform two steps to make small programs.

  • Link with the LIBCTINY.LIB library file
  • #include the "AggressiveOptimize.h" header file

Do only these two things and you don't need to bother with much of what I have presented. I guarantee that you will get results! Download the sample hello-world application to see how to start off.

1 Oct 2001

Posted in Tutorials

win32
Powered by Drupal


Undocumented CreateProcess | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
undoc01.zip

Published on 23 Jan 2012

1.2 Kb

Download

Undocumented CreateProcess

Undocumented and hard-to-find information regarding the CreateProcess API call

This tutorial forms a part of a new series which will concentrate on some of the non-GUI related issues in Windows programming. The subject of this tutorial will be the CreateProcess win32 API call. The tutorial is split into several sections, each one describing a neat fact about CreateProcess which you can use to your advantage. What I will describe cannot be found in Microsoft documentation, but has been discovered by many people over the years through alot of experimentation. All of the information collected here was found in various sources - especially older publications such as "Windows Developer Journal" from the mid-90s and also old USENET postings.

Before I start properly let's review what CreateProcess does, and how to use it in your code. If you are familiar with CreateProcess then please skip down to the first undocumented tip.

?
BOOL CreateProcess(
    LPCTSTR lpApplicationName,      // name of executable module
    LPTSTR lpCommandLine,          // command line string
    LPSECURITY_ATTRIBUTES lpProcessAttributes,    // SD
    LPSECURITY_ATTRIBUTES lpThreadAttributes,     // SD
    BOOL fInheritHandles,        // handle inheritance option
    DWORD dwCreationFlags,        // creation flags
    LPVOID lpEnvironment,          // new envirnment block
    LPCTSTR lpCurrentDirectory,     // current directory name
    LPSTARTUPINFO         lpStartupInfo,          // startup information
    LPPROCESS_INFORMATION lpProcessInformation    // process information
);

This function definition (above) was taken directly out of MSDN. The function is a bit unweildy and difficult to understand at first but process creation is a complicated business. Fortunately most of the parameters in CreateProcess can be omitted and the standard way of creating a new process looks like this:

?
STARTUPINFO          si = { sizeof(si) };
PROCESS_INFORMATION  pi;
char szExe[] = "cmd.exe";
if(CreateProcess(0, szExe, 0, 0, FALSE, 0, 0, 0, &si, &pi))
{
   // optionally wait for process to finish
   WaitForSingleObject(pi.hProcess, INFINITE);
   CloseHandle(pi.hProcess);
   CloseHandle(pi.hThread);
}

The example above simply starts a new instance of cmd.exe and lets it run. However, there are alot of options available in the call to CreateProcess which allow you to control how the program is started. Some of these options are specified directly as parameters, but most of the options are specified inside the STARTUPINFO structure which is passed as the 9th parameter:

?
typedef struct
{
    DWORD cb;
    LPTSTR lpReserved;
    LPTSTR lpDesktop;
    LPTSTR lpTitle;
    DWORD dwX;
    DWORD dwY;
    DWORD dwXSize;
    DWORD dwYSize;
    DWORD dwXCountChars;
    DWORD dwYCountChar;
    DWORD dwFillAttribute;
    DWORD dwFlags;
    WORD wShowWindow;
    WORD cbReserved2;
    LPBYTE lpReserved2;
    HANDLE hStdInput;
    HANDLE hStdOutput;
    HANDLE hStdError;
} STARTUPINFO;

The STARTUPINFO structure is also documented in MSDN, but some interesting information has been omitted by Microsoft which we will start to explore right now. The whole thrust of this article will centre around this STARTUPINFO structure, and the three "reserved" members of the STARTUPINFO structure - that is, lpReserved, lpReserved2 and cbReserved2.

Later on in this article I will explain what these three member variables are really used for, but first of all we will look at the dwFlags member variable and what it does. A quick reminder from MSDN is appropriate here:

Name | Value | Meaning
STARTF_USESHOWWINDOW | 0x01 | If this value is not specified, the wShowWindow member is ignored.
STARTF_USESIZE | 0x02 | If this value is not specified, the dwXSize and dwYSize members are ignored.
STARTF_USEPOSITION | 0x04 | If this value is not specified, the dwX and dwY members are ignored.
STARTF_USECOUNTCHARS | 0x08 | If this value is not specified, the dwXCountChars and dwYCountChars members are ignored.
STARTF_USEFILLATTRIBUTE | 0x10 | If this value is not specified, the dwFillAttribute member is ignored.
STARTF_RUNFULLSCREEN | 0x20 | Indicates that the process should be run in full-screen mode, rather than in windowed mode.
STARTF_FORCEONFEEDBACK | 0x40 | Indicates that the cursor is in feedback mode for two seconds after CreateProcess is called.
STARTF_FORCEOFFFEEDBACK | 0x80 | Indicates that the feedback cursor is forced off while the process is starting. The normal cursor is displayed.
STARTF_USESTDHANDLES | 0x100 | Sets the standard input, standard output, and standard error handles for the process to the handles specified in the hStdInput, hStdOutput, and hStdError members of the STARTUPINFO structure.

These nine values (or bit-flags) can be specified on their own or together by ORing them. The table above is pretty boring because apart from the START_USESTDHANDLES flag none of them are particularly useful. However, look at the range of values that the bit-flags use: the numbers go from 0x01 to 0x100 - this is only 9 bit-positions. There are 32bits in a single DWORD, so that leaves 23 possible bit-flags which havn't been defined yet.

That's more than enough background to get us started, so let's move onto more interesting things.

Detect if an executable was started from a Short-Cut

OK, the first trick I'm going to show you is how any win32 application can detect if it was started from a shortcut (i.e. by double-clicking a shortcut link), or directly via Windows explorer or the Run dialog. This trick is so simple it's amazing Microsoft hasn't documentated it:

There is an undocumented STARTUPINFO flag which I have called STARTF_TITLESHORTCUT. This bit-flag has the value 0x800. Windows sets this flag when an executable has been started via a short-cut. So it is possible for any program to check if it was started this way by examining it's own STARTUPINFO structure to see if this flag was specified:

?
// Returns TRUE if started through a shortcut, FALSE if not.
// Also returns the name of the shortcut file (if any)
BOOL GetShortcutName(TCHAR *szLinkName, UINT nLinkNameSize)
{
    STARTUPINFO si = { sizeof(si) };
    GetStartupInfo(&si);
    if(si.dwFlags & STARTF_TITLESHORTCUT)
    {
        lstrcpyn(szLinkName, si.lpTitle, nLinkNameSize);
        return TRUE;
    }
    return FALSE;
}

It is a simple matter to check for this undocumented flag, but another important piece of information should be explained. When the STARTF_TITLESHORTCUT flag is set, the lpTitle member of STARTUPINFO points to a string which contains the full path to the shortcut file used to start the current executable. So imagine that there is a short-cut on your desktop which launches notepad.exe. When notepad.exe starts, it's STARTUPINFO::lpTitle contains the following text:

?
C:\Documents and Settings\James\Desktop\Notepad.lnk

Pretty cool huh? Well I hope I've whet the appetite because we can move onto the next undocumented nugget!

Specify which monitor a process should start on

The next undocumented tip uses another unknown STARTUPINFO flag. This flag has the value 0x400, and I have given it the name STARTF_MONITOR.

When this flag is specified in the dwFlags member, the STARTUPINFO's hStdOutput member is used to specify a handle to a monitor, on which to start the new process. This monitor handle can be obtained by any of the multiple-monitor display functions (i.e. EnumDisplayMonitors, MonitorFromPoint, MonitorFromWindow etc).

?
BOOL CALLBACK EnumMonitorProc(HMONITOR hMonitor, HDC hdc, LPRECT rect, LPARAM data)
{
    STARTUPINFO          si = { sizeof(si) };
    PROCESS_INFORMATION  pi;
    char szExe[] = "sol.exe";
    // specify which monitor to use
    si.dwFlags     = STARTF_MONITOR;
    si.hStdOutput  = hMonitor;
    // start solitaire on specified monitor
    if(CreateProcess(0, szExe, 0, 0, FALSE, 0, 0, 0, &si, &pi))
    {
       CloseHandle(pi.hProcess);
       CloseHandle(pi.hThread);
    }
}
// call EnumMonitorProc (above) for every installed monitor on the system
void Demo()
{
    EnumDisplayMonitors(NULL, NULL, EnumMonitorProc, 0);
}

The example above uses the EnumDisplayMonitors API call as a method of obtaining a monitor handle (one for each monitor installed). The EnumMonitorProc function simply starts a new instance of Solitaire on each monitor in turn.

There are certain restrictions which should be noted here. You may be asking yourself why the above example works, when you know full well that the hStdOutput member should be used for a standard-output pipe. The answer to this question is simple - when the undocumented STARTF_MONITOR flag is specified, the STARTF_USESTDHANDLES flag is ignored. This means that these two flags cannot be used together, and the hStdInput, hStdOutput and hStdError handles act differently to how they are described in the documentation.

The next restriction is kind of obvious too: when you start a new process with CreateProcess, it has no concept of monitors, windows, or any other GUI related entities. A windows program actually has to physically create it's windows in order to display it's GUI. This is where the restriction for the STARTF_MONITOR flag comes into play. When a process calls CreateWindow, it can specify exactly where the window will appear by using the x, y, width and height parameters. This means that it is not possible for one program to specify where a second program's windows should appear unless this second program creates it's windows in a certain way.

?
hWnd = CreateWindow("ClassName",
                    "Title Text",
                    WS_OVERLAPPEDWINDOW,
                    CW_USEDEFAULT,              // X coordinate
                    CW_USEDEFAULT,              // Y coordinate
                    width,                      // Width
                    height,                     // Height
                    0, 0, 0, 0);
ShowWindow(hWnd, SW_SHOWDEFAULT);

It is only when a program creates a window using the CW_USEDEFAULT values for the coordinates, does the monitor specified by CreateProcess get used. The CreateWindow API call must obviously look inside the current process's STARTUPINFO structure to see what monitor it should create the window on. The Solitaire card-game was used in the example because it creates it's main window in this manner. I couldn't use Notepad as a demonstration because it didn't work - notepad seems to use actual coordinates to display it's main window rather than the CW_USEDEFAULT values.

Note that the ShellExecuteEx API call makes use of this CreateProcess feature in order to implement it's own monitor settings.

Starting screen-savers

In old versions of Microsoft's platform SDK documentation there was a flag called STARTF_SCREENSAVER - with the hex value 0x80000000. This flag is not documented anymore. When this flag is specified in CreateProcess' STARTUPINFO structure, the new process is started with NORMAL_PRIORITY. But, when this new process first calls GetMessage, it's priority is automatically lowered to that of IDLE_PRIORITY. This functionality seems vaguely useful for a screen-saver, and completely useless for any other situation. Presumably it is intended to allow a screen-saver's startup code to get up and running quickly, and then let the screensaver run "normally" without using too much CPU.

It appears that only the WinLogon process (winlogon.exe) is allowed to use STARTF_SCREENSAVER when activating the current screensaver, and this flag is next to useless for any other scenario.

Legacy Program Manager functionality

Do you remember Program Manager from the Windows 3.1 days? Program Manager still exists today even in Windows XP - just goto a command-prompt and type "progman" - the familiar program manager shell will pop up. Even in the days of Windows 3.1 (home and NT editions) the CreateProcess API contained undocumented functionality. I'm going to share this information with you now - even though this information is next to useless, it is still interesting to go over it.

If you look back at the STARTUPINFO structure definition, you can see that it has a member called lpReserved. This member has actually been in use for a long time now, but only by the Program Manager when it started executables.

The STARTUPINFO::lpReserved member is a pointer to a string buffer in the following format:

?
"dde.#,hotkey.#,ntvdm.#"

Whenever a new process is started by Program Manager, it can call GetStartupInfo to see how it was started. The resulting lpReserved member points to a string which contains three comma-separated fields, with the hash-signs (#) representing hexadecimal numbers.

  • The "dde." item specifies the DDE identifier which the child process can use to communicate with Program Manager. When the child process sends progman a WM_DDE_REQUEST message with this id, progman responds with a WM_DDE_DATA message. This message contains, amongst other things, the progman description, progman icon index and progman working directory for the child process.

    More information on this progman DDE interface can be found in the Microsoft Knowledge Base article 105446.

  • The "hotkey." item specifies the Program Manager hotkey sequence which was used to activate the process. This is a 16bit hex number, with the low byte representing the ASCII code of the hotkey, and the high byte representing the HOTKEYF_xxx values used by the WM_SETHOTKEY message. I have no idea why it is useful for a child process to know this information.
  • The "ntvdm." item is used to inform the NTVDM process about a program's properties in Program Manager. This is a single hex field which is made up of a combination of bit-flags. The value 1 indicates that there is a current directory, a value of 2 indicates that the item has a hotkey, and a value of 4 indicates that a title has been assigned to the program.

That just about covers the lpReserved member. It really isn't a useful feature any more, and even though it is possible to use lpReserved in a call to CreateProcess, no child process is ever going to make use of the information. You can see for yourself how Program Manager uses lpReserved by debugging a child process which has been executed via progman.exe.

Of course, if you have two applications and want to pass a text string from one app to the second (other than through the command-line) the lpReserved field could be useful.

Legacy ShellExecuteEx functionality

The ShellExecuteEx API has been around since Windows NT 3.1 (but not "normal" 3.1). This API call takes a single parameter - a pointer to a SHELLEXECUTEINFO structure. You can look in MSDN to see what this structure looks like (it's pretty boring). There are one or two interesting fields in this structure which are not at all well documented:

The first is the hMonitor member. ShellExecuteEx uses this value to control which monitor a new process should appear on. This is possible because ShellExecuteEx calls CreateProcess internally, and uses the same undocumented feature we covered earlier on (the STARTF_MONITOR flag).

The next field of interest is the hIcon member. MSDN documents this as being a handle to an icon for the new process, but I believe it might be an icon-index rather than an icon. I havn't been able to make use of this feature, and as far as I can tell only console applications running under Windows NT 3.1/3.5 made use of this feature. However, ShellExecuteEx calls CreateProcess with another undocumented setting. Again this is a new STARTUPINFO flag (I call it STARTF_ICON). Strangely this flag has the same value as the STARTF_MONITOR flag (0x400), and like before the STARTUPINFO::hStdOutput member is used to store the icon handle/index/whatever it is. You can observe this quite simply by writing a small program that calls ShellExecuteEx which specifies an icon for the new process, and watch the call to CreateProcess in a debugger. (The new program always ignores the icon though).

The last field of interest is the dwHotKey value. This is supposed to specify a hot-key for a child process, so that you can activate the application at any time by using the hotkey sequence at the keyboard - however, I have been unable to make this work. Again, ShellExecuteEx uses another undocumented STARTUPINFO flag. In this case the flag appears in winbase.h - it is called STARTF_USEHOTKEY and has the value 0x200. When this flag is specified in the STARTUPINFO::dwFlags member, the hStdInput member is supposed to be used as the hotkey value instead of the standard-input pipe. (See WM_SETHOTKEY for more information).

Both the icon and hotkey features of CreateProcess are a little strange, firstly because the functionality appears to be duplicated by the lpReserved parameter, and secondly the functionality is not present in modern versions of Windows. If anyone knows any more details about this subject I'd be very glad to hear them!

Pass arbitrary data to a child process!

The last undocumented trick is quite different to the previous ones so I thought I'd save it until last. The STARTUPINFO structure contains two members, lpReserved2 and cbReserved2:

?
WORD cbReserved2;
LPBYTE lpReserved2;

These two members provide a mechanism for passing arbitrary amounts of data from one process to another, without having to call VirtualAllocEx / WriteProcessMemory. The cbReserved2 member is a 16bit integer and specifies the size of the buffer pointed to by lpReserved2. This means that lpReserved2 can be as big as 65535 bytes.

The example below demonstrates how to pass a buffer from one process to another. When process-B is executed by process-A, it will display a message box saying "Hello from Process A!":

Process A:

?
int main()
{
    STARTUPINFO          si = { sizeof(si) };
    PROCESS_INFORMATION  pi;
    char buf[4444] = "Hello from Process A!";
    // setup the buffer to pass to child process
    si.lpReserved2 = buf;
    si.cbReserved2 = sizeof(buf);
    // create a new process with this data
    if(CreateProcess(0, szExe, 0, 0, 0, 0, 0, 0, &si, &pi))
    {
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }
}

Process B:

?
int mainCRTStartup()
{
    STARTUPINFO si = { sizeof(si) };
    GetStartupInfo(&si);
    // display what process A sent us
    MessageBox(NULL, si.lpReserved2, "Process B", MB_OK);
}

So far so good - we have a nice method for passing arbitrary binary data between applications, without having to use the command line. There is a problem though. In the example above process-B must be compiled with absolutely no C-runtime support. The reason is quite complicated but I will explain it now:

The Microsoft C runtime (including Visual Studio.NET) uses the lpReserved2 feature in it's implementation of the C functions: exec, system and spawn. When a new process is executed using these routines, the C-runtime has to be able to give the child process copies of it's open file handles (opened using fopen/open rather than CreateFile).

lpReserved2 is used as a mechanism to pass this file-handles information between programs compiled using MSVC. Before the exec/spawn runtime functions inevitably call CreateProcess, the lpReserved2 buffer is constructed using the following format:

?
DWORD count;
BYTE flags[count];
HANDLE handles[count];
// in memory these are layed out sequentially:
[ count ][ flags... ][ handles... ]

The first field in the lpReserved2 buffer is a 32bit count of the number of file-handles being passed. Next comes a BYTE-array of flags, one for each file-handle. These flags represent the file attributes that were used when the file was opened using fopen/open - i.e. read-only, write-append, text-mode, binary-mode etc. Immediately following the flags array is the HANDLE-array containing each open file. This array contains the actual file handle identifiers, again there are handle_count items in the array.

Any amount of data can follow this structure, up to a maximum of 65536 bytes. The cbReserved2 member must be set to the length (in bytes) of this structure. The actual steps need to construct the lpReserved2 are as follows:

  1. Any currently open "runtime" file-handles are enumerated.
  2. Each underlying win32 HANDLE is marked as inheritable.
  3. The number of file-handles being passed between processes is stored as the first 32bit integer in lpReserved2.
  4. The attributes of each open file-handle are stored sequentially in the flags BYTE-array.
  5. The file-handle integers are stored sequentially in the handles 32bit int-array.
  6. Finally CreateProcess is called, with the bInheritHandles parameter set to TRUE.

It is at this stage that the C-runtime becomes important. When the child process executes, as part of it's initialization before main() is called, the I/O runtime support needs to be initialized. During this initialization the C-runtime calls GetStartupInfo and checks to see if an lpReserved2 buffer has been specified. If lpReserved2 is not NULL (i.e. it points to valid memory) then the C-runtime parses the contents of the buffer and extracts the file-handles contained within - this is so the file-handles will be available to the new process.

  1. Call GetStartupInfo and check if lpReserved2 points to valid data.
  2. Extract the first 32bit integer - this is the number of file-handles in the buffer.
  3. Intialize the current process's I/O state with this number of open files.
  4. Loop over the flags[] array in lpReserved2.
  5. Loop over the handles[] array, populating the open-file table.

The problem might now be apparent to the more astute readers. Any program compiled using Microsoft's C-runtime will check it's lpReserved2 when it first starts - it should come as no surprise that this probably represents 90% of the C/C++ Windows programs in existence. The lpReserved2 member may also be used by other compiler vendors for the same purpose.

Should you wish to use lpReserved2 in your own programs (using CreateProcess instead of spawn/exec) you will need to be careful because the lpReserved2 buffer must be properly constructed. Failure to do so will result in the child processes crashing, or at the very least becoming unstable - the reason being that the child process is expecting to find lpReserved2 in a particular format.

Getting around this problem is simple. Setting the first 4 bytes in lpReserved to zeros (nulls) indicates that the handle-arrays are empty, and any c-runtime startup code will simply skip this phase. Your arbitrary binary data can come immediately after this zero-marker. The code now looks like this:

Process A:

?
int main()
{
    STARTUPINFO          si = { sizeof(si) };
    PROCESS_INFORMATION  pi;
    char buf[4444];
    // construct the lpReserved2 buffer
    *(DWORD *)buf = 0;
    lstrcpy(buf+sizeof(DWORD), "Hello from Process A!";
    // setup the buffer to pass to child process
    si.lpReserved2 = buf;
    si.cbReserved2 = sizeof(buf);
    // create a new process with this data
    if(CreateProcess(0, szExe, 0, 0, 0, 0, 0, 0, &si, &pi))
    {
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }
}

Process B:

?
int main()
{
    STARTUPINFO si = { sizeof(si) };
    GetStartupInfo(&si);
    // display what process A sent us
    if(si.lpReserved2 != NULL)
    {
        MessageBox(NULL, si.lpReserved2 + sizeof(DWORD), "Process B", MB_OK);
    }
}

The example above can now be compiled without having to worry about C-runtime considerations.

Note: Apparently this method does not work under 64bit Windows Vista.

CreateProcess Summary

This was more of a programming article than a tutorial. Hopefully you have either found something useful, or have discovered something about CreateProcess which you didn't know before. Just to recap here is a summary of the undocumented CreateProcess features:

Undocumented STARTUPINFO flags:

Name | Value | Meaning
STARTF_USEHOTKEY | 0x200 | Use the hot-key DWORD specified in the hStdInput member.
STARTF_MONITOR | 0x400 | Use the HMONITOR handle specified in the hStdOutput member.
STARTF_ICON | 0x400 | Use the HICON specified in the hStdOutput member (incompatible with STARTF_MONITOR).
STARTF_TITLESHORTCUT | 0x800 | Program was started through a shortcut. The lpTitle contains the shortcut path.
STARTF_SCREENSAVER | 0x80000000 | Start the program with NORMAL_PRIORITY, then drop to IDLE_PRIORITY.

lpReserved - pointer to a string containing the text "dde.#,hotkey.#,ntvdm.#". This only occurs when a process is started through the legacy Program Manager shell. Use this member to pass your own string buffer to a child process.

lpReserved2 and cbReserved2 - pointer to a buffer containing arbitrary binary data to pass to the child process. To be safe ensure that the first four bytes of lpReserved2 are always zero. Has issues under 64bit Vista.

I'd really like to hear any comments you have on this article, or any more information which I might have presented incorrectly or omitted entirely.

6 Jan 2004

Posted in Tutorials

win32
Powered by Drupal


OLE Drag and Drop | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

OLE Drag and Drop

The aim of this series of tutorials is to enable the reader to implement complete drag and drop support for their applications. Drag and drop became a standard feature of many Windows applications when Windows 95 was launched. With COM and OLE becoming more mainstream at this time, applications had the power to interact seamlessly with the Windows Shell and even other Windows applications. This flexibility came at a price though. Writing any form of COM or OLE enabled appliction is a complete nightmare, to put it mildly.

  • Drag and Drop Introduction
  • OLE Data Transfers
  • Implementing IDataObject
  • Enumerating FORMATETC
  • Drop Source
  • Drop Target
Drag and Drop Introduction ›

15 Feb 2004

Posted in Tutorials

win32

OLE Drag and Drop

  • Drag and Drop Introduction
  • OLE Data Transfers
  • Implementing IDataObject
  • Enumerating FORMATETC
  • Drop Source
  • Drop Target
Powered by Drupal


Drag and Drop Introduction | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Drag and Drop Introduction

OLE Drag and Drop

The aim of this series of tutorials is to enable the reader to implement complete drag and drop support for their applications. Drag and drop became a standard feature of many Windows applications when Windows 95 was launched. With COM and OLE becoming more mainstream at this time, applications had the power to interact seamlessly with the Windows Shell and even other Windows applications. This flexibility came at a price though. Writing any form of COM or OLE enabled appliction is a complete nightmare, to put it mildly.

This tutorial will ease you through the pain of writing the required OLE interfaces necessary to enable drag and drop behaviour. As usual, we will use pure Win32 API techniques. However, I will be using C++ rather than C, because C++ is definitely the preferred language to write COM interfaces with. It would be a simple matter to convert the code to C as I will also explain.

I intend this tutorial to be written in several parts, as there is alot of information to cover. Also, the components of drag-and-drop lend themselves very nicely to separate topics, so this is the approach I will use. The first tutorial (this one, in fact) will provide an introduction to OLE Drag and Drop. Subsequent tutorials will focus on drag-and drop. Parts 2 and 3 will cover OLE data transfers the IDataObject interface. Part 4 will look at the IEnumFORMATETC interface, whilst parts 5 and 6 will cover drag-sources and drop-targets.

Once we have covered these basic details there will be further tutorials discussing drag-and-drop in more detail.

Additional Reading

I strongly suggest you investigate the following sources of information, because this is where I learnt about COM, OLE and drag&drop.

?
ftp ftp.microsoft.com
username "ftp"
password "ftp"
cd softlib/mslfiles
bin
get drgdrps.exe
get drgdrpt.exe
bye
  1. msdn.microsoft.com
    The starting place for everything win32 related.
  2. Inside OLE 2nd edition has alot of very useful information, and is regarded as the "bible" for OLE. It's a little dated but contains everything you need to know. A "softcopy" version of this book used to be included in MSDN, and maybe it still is. There are also alot of softcopy versions in pdf and chm format floating around the internet.
  3. ftp.microsoft.com
    Microsoft's FTP server contains hundreds of resources dating years back. The most useful thing I've found there so far are two small files - drgdrps.exe and drgdrpt.exe. These are self-extracting ZIP files which contain full source to a simple drop-source and drop-target application. To access these files simply goto your command prompt and type:
  4. Microsoft Technical Articles - in particular the "OLE for Idiots" series, and "What OLE Is Really About". These articles are quite old now (getting on for 10 years or so) but are still relevant today. A simple google search will locate these articles.

OLE Drag and Drop

"Drag and Drop" is a term used to describe the action of using a mouse to transfer data from one place to another.

Every Drag and Drop operation is comprised of three elements. These elements are of course COM objects which must be implemented by any application that wants to support drag and drop.

  • The Source of the drag-drop is represented by the IDropSource interface. The IDropSource contains methods for generating visual feedback, and for cancelling or completing the drag-drop operation.
  • The Target of the drag-drop is represented by the IDropTarget interface.
  • The Data being transferred is represented by the IDataObject interface.

Note that an application doesn't need to support all three COM interfaces - if you just want to make a "drop target", then only the IDropTarget interface is required. Similarly, an application which just supports dragging data from it (and not to it) is required to support the IDropSource and IDataObject interfaces. Of course, an application can implement all three interfaces to support full drag and drop within the same document.

The diagram above illustrates the key components required to support a drag and drop operation. Take a moment to understand what the diagram is presenting. The box on the left is the initiator of a drag-and-drop operation. It has created two COM objects which each expose an interface (IDataObject and IDropSource), which are used by OLE to perform the drag-drop.

The box on the right represents the destination of the drag-and-drop operation. It has created a single COM object (with a single interface IDropTarget). Whenever the mouse is dragged over a drop target window, OLE passes an IDataObject interface to the target which allows the target to "see" the data object exposed by the source. The object doesn't get copied in any way - just the COM interface is made available. When the target extracts data from the data object, the OLE/COM runtime takes care of marshalling the function calls and data across the process boundaries.

In this example the source and destination can be either implemented within the same process, or can be in seperate processes. It isn't important where they are implemented, because the OLE runtime (COM, in fact) takes care of making the Data Object available to the destination process.

Starting Drag and Drop

The first task any application must perform when it wants to use the OLE functions is to call OleInitialize when it starts, and OleUnintialize when it finishes. This is not quite accurate though - it is better to say the thread that wants to use OLE must call these functions, because COM and OLE must be initialized and uninitialized on a per thread basis:

?
WINOLEAPI OleInitialize(LPVOID pvReserved);
WINOLEAPI OleUninitialize();

At the very heart of OLE drag and drop is an API called DoDragDrop. The function prototype has the following form:

?
WINOLEAPI DoDragDrop(
   IDataObject  * pDataObject,   // Pointer to the data object
   IDropSource  * pDropSource,   // Pointer to the source
   DWORD dwOKEffect,    // Effects allowed by the source
   DWORD * pdwEffect      // Pointer to effects on the source
   );

When an application wants to initiate a drag and drop operation it must call this function. However there are two important steps which must take place before DoDragDrop is called.

Both the IDataObject and IDropSource objects must be created by the initiator of the drag-drop operation, before calling DoDragDrop. Creating these two objects is non-trivial and is therefore covered in the next section of this tutorial. Note that no mention of GUI related objects (such as windows) has been made so far. A drop source is a stand-alone entity that is not tied to any one window, although usually a drag-drop operation is initiated when a window procedure processes a WM_MOUSEMOVE message.

When DoDragDrop is called, a modal message loop is entered which monitors mouse and keyboard messages.

Receiving Drag and Drop data

When an application wants to be the recipient of a drag-and-drop operation, it must call the RegisterDragDrop function. Of course, this application must call OleInitialize/OleUninitialize in the same way the source application must.

?
WINOLEAPI RegisterDragDrop(
   HWND hwnd,          // Handle to a window that can accept drops
   IDropTarget  * pDropTarget    // Pointer to object that is to be target of drop
   );

Looking at the function prototype above reveals the last component to a drag-drop-operation - the IDropTarget COM interface. Also required is a handle to a window, which RegisterDragDrop requires in addition to the IDropTarget interface. This window is registered with the OLE runtime, so that when the mouse is dragged over this window, OLE can call the methods in the IDropTarget interface to notify the application owning that window that a drag-drop operation is in progress.

When a window is to be destroyed, the RevokeDragDrop API must be called:

?
WINOLEAPI RevokeDragDrop(
   HWND hwnd                  // Handle to a window that can accept drops
   );

This API unregisters the specified window with the OLE runtime, and releases the IDropTarget interface used during registration and releases the DropTarget object in the process.

Coming up in Part 2 - OLE Data Transfers

So far I have presented an overview of OLE drag and drop. There is still alot more ground to cover, so over the next parts of this tutorial we will be looking at the core components of drag and drop. Coming up is the subject of OLE data transfers, but for now it would be a good idea to visit http://msdn.microsoft.com and review the documentation for DoDragDrop and RegisterDragDrop.

‹ OLE Drag and Drop up OLE Data Transfers ›

15 Feb 2004

Posted in Tutorials

win32

OLE Drag and Drop

  • Drag and Drop Introduction
  • OLE Data Transfers
  • Implementing IDataObject
  • Enumerating FORMATETC
  • Drop Source
  • Drop Target
Powered by Drupal


Implementing IDataObject | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
dataobj.zip

Published on 22 Jan 2012

5.82 Kb

Download
dataobjview.zip

Published on 22 Jan 2012

11.29 Kb

Catch22OLEPart3.rar

Published on 22 Jan 2012

6.99 Kb

Implementing IDataObject

OLE Drag and Drop

Updated 6 Dec 2006

Many thanks to Davide Chiodi from Italy who has very kindly converted the data-obect code into a Pure C implementation - the download link is available at the bottom of this article!

In the last part of the tutorial we looked at how to access the Windows clipboard using OLE and the IDataObject. In this part we will be implementing the IDataObject interface, and using our completed data object to store the text "Hello World" into the Windows clipboard.

Creating a COM interface - IDataObject

In order to create our own COM object, we need to define a C++ class which implements all of these functions, and in order for the COM virtual-function table to be automatically included for us, we will use C++ class inheritance:

?
class CDataObject : public IDataObject
{
public:
    // IUnknown members
    HRESULT __stdcall QueryInterface (REFIID iid, void ** ppvObject);
    ULONG __stdcall AddRef (void);
    ULONG __stdcall Release (void);
    // IDataObject members
    HRESULT __stdcall GetData               (FORMATETC *pFormatEtc,  STGMEDIUM *pmedium);
    HRESULT __stdcall GetDataHere           (FORMATETC *pFormatEtc,  STGMEDIUM *pmedium);
    HRESULT __stdcall QueryGetData          (FORMATETC *pFormatEtc);
    HRESULT __stdcall GetCanonicalFormatEtc (FORMATETC *pFormatEct,  FORMATETC *pFormatEtcOut);
    HRESULT __stdcall SetData               (FORMATETC *pFormatEtc,  STGMEDIUM *pMedium,  BOOL fRelease);
    HRESULT __stdcall EnumFormatEtc         (DWORD dwDirection, IEnumFORMATETC **ppEnumFormatEtc);
    HRESULT __stdcall DAdvise               (FORMATETC *pFormatEtc,  DWORD advf, IAdviseSink *, DWORD *);
    HRESULT __stdcall DUnadvise             (DWORD dwConnection);
    HRESULT __stdcall EnumDAdvise           (IEnumSTATDATA **ppEnumAdvise);
    // Constructor / Destructor
    CDataObject(FORMATETC *fmtetc, STGMEDIUM *stgmed, int count);
    ~CDataObject();
private:
    // any private members and functions
    LONG m_lRefCount;
    int LookupFormatEtc(FORMATETC *pFormatEtc);
};

Notice that all of the IDataObject members have been listed - even the IUnknown interface members. This is because we are now implementing an entire COM object, so every member function must be included in the correct order.

With the IUnknown functions already visited in a previous tutorial, we can move onto the IDataObject functions. There is some good news and some bad news. The good news is, not all of the functions need to be implemented! Out of the nine functions, only three are required for OLE drag and drop, so this cuts down our work enormously.

The bad news is once we've implemented the IDataObject methods, we need to implement an entirely separate COM interface - the IEnumFORMATETC interface. We're a little way off this step yet, so let's start with simply allocating a new instance of IDataObject.

Constructing IDataObject

The IDataObject's main task is to allow a "consumer" to query it for data. These queries will take the form of calls to either QueryData or EnumFormatEtc. Therefore the IDataObject needs to know what data formats it should store, and when a consumer asks for the data, it should be able to provide it.

We therefore need to find some method to populate the IDataObject with real pieces of data and also tell it what the data is, in the form of FORMATETC structures.

The IDataObject will be populated with data during the call to it's C++ class constructor. For more flexibility it may make sense to use the IDataObject::SetData routine to perform this task, but for our simple implementation using the constructor makes sense for now.

?
CDataObject::CDataObject(FORMATETC *fmtetc, STGMEDIUM *stgmed, int count)
{
    // reference count must ALWAYS start at 1
    m_lRefCount    = 1;
    m_nNumFormats  = count;
    m_pFormatEtc   = new FORMATETC[count];
    m_pStgMedium   = new STGMEDIUM[count];
    for(int i = 0; i < count; i++)
    {
        m_pFormatEtc[i] = fmtetc[i];
        m_pStgMedium[i] = stgmed[i];
    }
}

The constructor performs two important tasks. The first is to initialize the COM object's reference count to 1. I see alot of incorrect COM code where reference counts begin at zero. The COM specifications clearly state that a COM object must begin life with a reference count of 1. If you think about it, a reference count of zero means that the COM object should be deleted, so it should never be initialized to this value.

The second task is to make a private copy of the FORMATETC and STGMEDIUM structures specified in the class constructor. The data object won't take ownership of the data inside each STGMEDIUM structure, it will merely reference it, and duplicate the data only when requested during a call to GetData.

Creating IDataObject

Now that we have a well-defined constructor for IDataObject, we can write a wrapper function which will hide the class details:

?
HRESULT CreateDataObject(FORMATETC *fmtetc, STGMEDIUM *stgmeds, UINT count, IDataObject **ppDataObject)
{
    if(ppDataObject == 0)
        return E_INVALIDARG;
    *ppDataObject = new CDataObject(fmtetc, stgmeds, count);
    return (*ppDataObject) ? S_OK : E_OUTOFMEMORY;
}

So creating an IDataObject is now very simple:

?
FORMATETC fmtetc = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stgmed = { TYMED_HGLOBAL, { 0 }, 0 };
stgmed.hGlobal = StringToHandle("Hello, World!");
IDataObject *pDataObject;
CreateDataObject(&fmtetc, &stgmed, 1, &pDataObject);

Alot of implementations of IDataObject include alot of application-specific code inside the interface which performs the memory allocations. The idea behind this implementation is to provide a generic IDataObject which can be used in a variety of different applications. OK, so a little bit of work needs to be done up-front to create the FORMATETC and STGMEDIUM structures before creating the data object, but this can be easily isolated and doesn't pollute the interface code.

IDataObject::QueryGetData

This member function is called whenever an application wants to test our IDataObject to see if it contains a specific type of data. A pointer to a FORMATETC structure is passed as an argument, and it is the task of IDataObject::QueryGetData to inspect this structure and return a value to indicate if the requested data is available or not.

?
HRESULT __stdcall CDataObject::QueryGetData(FORMATETC *pFormatEtc)
{
    return (LookupFormatEtc(pFormat) == -1) ? DV_E_FORMATETC : S_OK;
}

The QueryGetData function is very simple in this case. We pass off all the work to a private helper function - LookupFormatEtc:

?
int CDataObject::LookupFormatEtc(FORMATETC *pFormatEtc)
{
    // check each of our formats in turn to see if one matches
    for(int i = 0; i < m_nNumFormats; i++)
    {
        if((m_pFormatEtc[i].tymed    &  pFormatEtc->tymed)   &&
            m_pFormatEtc[i].cfFormat == pFormatEtc->cfFormat &&
            m_pFormatEtc[i].dwAspect == pFormatEtc->dwAspect)
        {
            // return index of stored format
            return i;
        }
    }
    // error, format not found
    return -1;
}

The helper function above tries to match the specified FORMATETC structure against one of the available structures belonging to our data object. If it finds one that matches, it simply returns an index to the appropriate entry in the m_pFormatEtc array. If no match is found, an error value of -1 is returned.

Note the use of the bitwise-AND operator in the if-clause:

?
if( m_pFormatEtc[i].tymed & pFormatEtc->tymed )

The AND operator is used here because the FORMATETC::tymed member is actually a bit-flag which can contain more than one value. For example, the caller of QueryGetData could quite legitimetly specify a FORMATETC::tymed value of (TYMED_HGLOBAL | TYMED_ISTREAM), which basically means "Do you support HGLOBAL or IStream?".

IDataObject::GetData

The GetData function is similar in many ways to QueryGetData, the exception being that if the requested data format is supported, it must be returned into the specified storage-medium structure.

?
HRESULT __stdcall CDataObject::GetData (FORMATETC *pFormatEtc, STGMEDIUM *pStgMedium)
{
    int idx;
    // try to match the specified FORMATETC with one of our supported formats
    if((idx = LookupFormatEtc(pFormatEtc)) == -1)
        return DV_E_FORMATETC;
    // found a match - transfer data into supplied storage medium
    pMedium->tymed           = m_pFormatEtc[idx].tymed;
    pMedium->pUnkForRelease  = 0;
    // copy the data into the caller's storage medium
    switch(m_pFormatEtc[idx].tymed)
    {
    case TYMED_HGLOBAL:
        pMedium->hGlobal     = DupGlobalMem(m_pStgMedium[idx].hGlobal);
        break;
    default:
        return DV_E_FORMATETC;
    }
    return S_OK;
}

The same internal helper function LookupFormatEtc is used to check if the requested data format is supported. If it is, then the appropriate STGMEDIUM data is copied into the caller-supplied structure.

Note that call to the DupGlobalMem routine. This is a helper function which returns a duplicate of the specified HGLOBAL memory handle, and is required because each call to GetData must result in a fresh copy of the data.

?
HGLOBAL DupGlobalMemMem(HGLOBAL hMem)
{
    DWORD len    = GlobalSize(hMem);
    PVOID source = GlobalLock(hMem);
    PVOID dest   = GlobalAlloc(GMEM_FIXED, len);
    memcpy(dest, source, len);
    GlobalUnlock(hMem);
    return dest;
}

We will need similar routines to support the other TYMED_xxx storage types. For now the only additional format I imagine being implemented is IStream.

IDataObject::EnumFormatEtc

This is the last member that requires any real programming effort. Its unfortunate that it whilst this member function is so simple to implement, it also requires us to start writing the IEnumFORMATETC object as well.

?
HRESULT __stdcall CDataObject::EnumFormatEtc (DWORD dwDirection, IEnumFORMATETC **ppEnumFormatEtc)
{
    // only the get direction is supported for OLE
    if(dwDirection == DATADIR_GET)
    {
        // for Win2k+ you can use the SHCreateStdEnumFmtEtc API call, however
        // to support all Windows platforms we need to implement IEnumFormatEtc ourselves.
        return CreateEnumFormatEtc(m_NumFormats, m_FormatEtc, ppEnumFormatEtc);
    }
    else
    {
        // the direction specified is not supported for drag+drop
        return E_NOTIMPL;
    }
}

If you look at the code comment above, you can see mention of the SHCreateStdEnumFmtEtc API call. What this does is create an IEnumFORMATETC interface on our behalf, requiring no work from ourselves. Unfortunately this API is only available on Windows 2000 and above, so we have to provide an alternative method to create an IEnumFORMATETC object.

Therefore in the next tutorial we will provide a full implementation of CreateEnumFormatEtc, a replacement for the Shell API call.

Unsupported IDataObject functions

There are still a number of IDataObject functions that need to be implemented. Whilst every function must be a valid routine, there is a simple method to indicate to OLE that we don't support the functionality that these routines might offer outside the world of drag and drop.

The IDataObject::DAdvise, IDataObject::EnumDAdvise and IDataObject::DUnadvise functions simply need to return the value OLE_E_ADVISENOTSUPPORTED.

?
HRESULT CDataObject::DAdvise (FORMATETC *pFormatEtc, DWORD advf, IAdviseSink *pAdvSink,
                                                                 DWORD *pdwConnection)
{
    return OLE_E_ADVISENOTSUPPORTED;
}
HRESULT CDataObject::DUnadvise (DWORD dwConnection)
{
    return OLE_E_ADVISENOTSUPPORTED;
}
HRESULT CDataObject::EnumDAdvise (IEnumSTATDATA **ppEnumAdvise)
{
    return OLE_E_ADVISENOTSUPPORTED;
}

IDataObject::GetDataHere can only be implemented if the IStream and IStorage interfaces are supported by the data object. In our case we only support HGLOBAL data, so returning DATA_E_FORMATETC seems a sensible choice.

?
HRESULT CDataObject::GetDataHere (FORMATETC *pFormatEtc, STGMEDIUM *pMedium)
{
    return DATA_E_FORMATETC;
}

IDataObject::SetData and IDataObject::GetCanonicalFormatEtc are also simple to implement - the value E_NOTIMPL can be returned in this case. One special note about GetCanonicalFormatEtc - even though we return an error value, the output FORMATETC structure's "ptd" member (the pointer-to-DVTARGETDEVICE) must be set to zero:

?
HRESULT CDataObject::GetCanonicalFormatEtc (FORMATETC *pFormatEct, FORMATETC *pFormatEtcOut)
{
    // Apparently we have to set this field to NULL even though we don't do anything else
    pFormatEtcOut->ptd = NULL;
    return E_NOTIMPL;
}
HRESULT CDataObject::SetData (FORMATETC *pFormatEtc, STGMEDIUM *pMedium,  BOOL fRelease)
{
    return E_NOTIMPL;
}

Adding Data to the Clipboard

OK, so here's a little program to add "Hello World" to the Windows clipboard using OLE and data objects.

?
#include <windows.h>
int main(void)
{
    OleInitialize(0);
    IDataObject *pDataObject;
    FORMATETC fmtetc = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    STGMEDIUM stgmed = { TYMED_HGLOBAL, { 0 }, 0 };
    stgmed.hGlobal = StringToHandle("Hello, World!", -1);
    // create the data object
    if(CreateDataObject(&fmtetc, &stgmed, 1, &pDataObject) == S_OK)
    {
        // add data to the clipboardOleSetClipboard(pDataObject);
        OleFlushClipboard();
        pDataObject->Release();
    }
    // cleanup
    ReleaseStgMedium(&stgmed);
    OleUninitialize();
    return 0;
}

Unfortunately this program won't work yet because we havn't implemented IEnumFORMATETC and the CreateEnumFormatEtc function. If you hold on for a moment though...

Coming up in Part 4 - Implementing IEnumFORMATETC

The next part of this tutorial series will be dedicated to writing a single function CreateEnumFormatEtc, which will be a drop-in replacement for the SHCreateStdEnumFmtEtc API call. Our implementation will have exactly the same semantics and will return a pointer to a genuine IEnumFORMATETC COM object which will be fully detailed.

‹ OLE Data Transfers up Enumerating FORMATETC ›

15 Jun 2004

Posted in Tutorials

win32

OLE Drag and Drop

  • Drag and Drop Introduction
  • OLE Data Transfers
  • Implementing IDataObject
  • Enumerating FORMATETC
  • Drop Source
  • Drop Target
Powered by Drupal


OLE Data Transfers | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

OLE Data Transfers

OLE Drag and Drop

Welcome to the second article in the "OLE Drag and Drop" tutorial series! The purpose of this article is to explore how data is represented and transferred between applications in the OLE environment.

The very heart of OLE data transfers is the IDataObject COM interface. An IDataObject provides a method of transferring and accessing data from one application to another. The most common use of OLE data transfers is the Windows clipboard, and of course drag and drop. The IDataObject is effectively a COM wrapper around one or more items of data.

Before we look at the IDataObject in any detail, there are two very important data structures with which you must become familar: the FORMATETC and STGMEDIUM structures, which are used to describe and store OLE data.

Describing OLE data

The FORMATETC structure (pronounced "format et cetera") is used to identify the type of data that an IDataObject can supply (or receive). It is basically an extension of the standard Windows clipboard formats (CF_TEXT etc). So in addition to the basic clipboard format, the FORMATETC structure also describes how the data should be rendered and stored.

?
typedef struct
{
    CLIPFORMAT      cfFormat;     // Clipboard format
    DVTARGETDEVICE *ptd;          // (NULL)       Target device for rendering
    DWORD dwAspect;     // (DV_CONTENT) How much detail is required for data rendering
    LONG lindex;       // (-1)         Used when data is split across page boundaries
    DWORD tymed;        // Storage medium used for data transfer (HGLOBAL, IStream etc)

} FORMATETC;

The members of the FORMATETC structure are described below.

This value will almost always be -1.

  • cfFormat: The clipboard format which is used to identify the FORMATETC structure. This can either be a built-in format such as CF_TEXT or CF_BITMAP, or a custom format registered with RegisterClipboardFormat.
  • ptd: Pointer to a DVTARGETDEVICE structure, which provides information about the device for which the data has been rendered. For normal clipboard operations and drag and drop, this will usually be NULL.
  • dwAspect: Describes the amount of detail used to render the data. Usually this will be DVASPECT_CONTENT, meaning "full content", but could describe a lesser detail such as thumbnail or icon.
  • lindex: Is only used when data is to be split across page boundaries, and is not used for simple OLE transfers.
  • tymed: This is the interesting member, because it describes the type of "storage medium" used to hold the data. This member has taken it's name from the words "Type of Medium" - i.e. ty...med. The value is taken from on of the TYMED_xxx values defined in windows.h

So with this single data structure, OLE has provided a method to describe to a "consumer" what the data is, and how it is intended to be rendered.

Storing OLE data

The STGMEDIUM structure (short for STORAGE MEDIUM) provides a container in which to actually hold data - hence the term storage medium.

?
typedef struct
{
    DWORD tymed;

    union
    {
        HBITMAP hBitmap;
        HMETAFILEPICT  hMetaFilePict;
        HENHMETAFILE hEnhMetaFile;
        HGLOBAL hGlobal;
        LPWSTR lpszFileName;
        IStream        *pstm;
        IStorage       *pstg;
    };

    IUnknown *pUnkForRelease;

} STGMEDIUM;

The structure definition above might look complicated, but there are in effect only three members, because the "unnamed" union collects all of it's contents as one entity sharing the same space within the STGMEDIUM structure.

  • tymed: This must be the same as tymed in the FORMATETC structure - this member specifies what medium has been used to store the data - i.e. global data (TYMED_HGLOBAL), IStream (TYMED_ISTREAM) etc. The corresponding element in the union is the "handle" to the data.
  • hBitmap/hGlobal etc: The actual data. Only one of these will be valid, depending on the value of tymed.
  • pUnkForRelease: An optional pointer to an IUnknown interface on which the receipient of the data should call Release. When this field is NULL, it is the receipient's responsibility to release the memory handle. The ReleaseStgMedium API call is useful here because it takes care of releasing the STGMEDIUM's data contents, so in fact no work is required on our part.

The STGMEDIUM structure is basically an extension of the traditional Windows HGLOBAL memory handle. Whilst the HGLOBAL is still supported (and is still the most common!), many other types of storage are supported, the most useful being the IStream and IStorage generic COM interfaces.

So in conclusion, the FORMATETC and STGMEDIUM structures are used in conjunction to describe and store an OLE data entity. The FORMATETC is usually used to request a specific type of data from an IDataObject, whilst the STGMEDIUM structure is used to receive and hold the requested data.

Transferring OLE data

The IDataObject interface provides a method to transfer data from one application to another. An IDataObject is very useful for two situations - Clipboard transfers and Drag and Drop. With a careful design, it is therefore possible to implement both clipboard and drag-and-drop support with a single COM object.

The following table lists the IDataObject member functions in the order they must appear in the interface v-table. The IUnknown methods (AddRef, Release and QueryInterface) have been removed for brevity.

IDataObject Methods | Description
GetData | Renders the data described in a FORMATETC structure and transfers it through the STGMEDIUM structure.
GetDataHere | Renders the data described in a FORMATETC structure and transfers it through the STGMEDIUM structure allocated by the caller.
QueryGetData | Determines whether the data object is capable of rendering the data described in the FORMATETC structure.
GetCanonicalFormatEtc | Provides a potentially different but logically equivalent FORMATETC structure.
SetData | Provides the source data object with data described by a FORMATETC structure and an STGMEDIUM structure.
EnumFormatEtc | Creates and returns a pointer to an IEnumFORMATETC interface to enumerate the FORMATETC objects supported by the data object.
DAdvise | Creates a connection between a data object and an advise sink so the advise sink can receive notifications of changes in the data object.
DUnadvise | Destroys a notification previously set up with the DAdvise method.
EnumDAdvise | Creates and returns a pointer to an interface to enumerate the current advisory connections.

The table above looks pretty overwhelming, and it gets even worse when we look at the EnumFormatEtc method and discover that we also have to implement the IEnumFORMATETC interface as well! Thats a total of thirteen member functions, not including the IUnknown methods - and we havn't even begun to look at IDropSource and IDropTarget!

Fortunately for simple OLE drag&drop, only the GetData, QueryGetData and EnumFormatEtc members are required so that saves us alot of work.

Accessing the Clipboard using IDataObject

To ease ourselves into the way OLE works, we will begin with a simple program which will access the clipboard using OLE.

?
WINOLEAPI OleGetClipboard(IDataObject ** ppDataObj);

This single Windows API call is used to retrieve an IDataObject, which provides a nice interface to cleanly access the Window's clipboard content. Note that we don't have to implement the IDataObject interface in this case, we just need to know how to interface with it. A simple program to access the clipboard contents is shown below:

?
#include <windows.h>
int main(void)
{
    IDataObject *pDataObject;
    // Initialize COM and OLE
    if(OleInitialize(0) != S_OK)
        return 0;
    // Access the data on the clipboard
    if(OleGetClipboard(&pDataObject) == S_OK)
    {
        // access the IDataObject using a separate function
        DisplayDataObject(pDataObject);
        pDataObject->Release();
    }
    // Cleanup
    OleUninitialize();
    return 0;
}

The OLE API calls are very simple, and it is also straight-forward to programmatically access an IDataObject:

?
void DisplayDataObject(IDataObject *pDataObject)
{
    FORMATETC fmtetc = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    STGMEDIUM stgmed;
    // ask the IDataObject for some CF_TEXT data, stored as a HGLOBAL
    if(pDataObject->GetData(&fmtetc, &stgmed) == S_OK)
    {
        // We need to lock the HGLOBAL handle because we can't
        // be sure if this is GMEM_FIXED (i.e. normal heap) data or not
        char *data = GlobalLock(stgmed.hGlobal);
        printf("%s\n", data);
        // cleanup
        GlobalUnlock(stgmed.hGlobal);
        ReleaseStgMedium(&stgmed);
    }
}

The code above demonstrates the most common method used to access an IDataObject. The data is requested using IDataObject::GetData. We constructed a FORMATETC object which was used to specify exactly what type of data we wanted - in this case, a standard CF_TEXT buffer of data, stored as a regular HGLOBAL memory object.

The data is returned into the STGMEDIUM structure that we provided. Once we lock and display the data it is a simple matter to cleanup and call the standard ReleaseStgMedium API, to release the data stored inside the STGMEDIUM structure.

Note that the code sample will only work when there is text selected into the Windows clipboard - that is, if there is no CF_TEXT stored in the clipboard, the clipboard's IDataObject::GetData routine will return a failure code and we won't print anything.

Coming up in Part 3 - Implementing IDataObject

OK, so we still havn't actually performed any drag and drop, or even implemented a single COM interface yet. All this is going to change in Part 3 of the tutorial, where we will implement our very own IDataObject and store it on the Windows clipboard. Once we've accomplished this (no mean feat!) we will be ready to start dragging and dropping to our heart's content.

‹ Drag and Drop Introduction up Implementing IDataObject ›

15 Jun 2004

Posted in Tutorials

win32

OLE Drag and Drop

  • Drag and Drop Introduction
  • OLE Data Transfers
  • Implementing IDataObject
  • Enumerating FORMATETC
  • Drop Source
  • Drop Target
Powered by Drupal


Enumerating FORMATETC | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
enumformat.zip

Published on 22 Jan 2012

1.82 Kb

Download
dataobjview.zip

Published on 22 Jan 2012

11.29 Kb

Enumerating FORMATETC

OLE Drag and Drop

This tutorial will concentrate on implementing a COM object which exposes the IEnumFORMATETC interface. There are two code downloads this time. The first includes a complete implementation of a generic IEnumFORMATETC which you can use in your applications.

The second code download is the full source code to an application called "IDataObject Viewer". This is an replacement for the PlatformSDK program of the same name. It is basically a demonstration of how to use the IEnumFORMATETC interface rather than write it. More important though, it is a very useful tool for debugging OLE drag and drop code because you can drag anyform of IDataObject onto it, and it will display the available formats of data contained within it. Give it a go!

The IEnumFORMATETC interface is quite often overlooked when beginning drag and drop. In some cases it is not necessary, but to be 100% sure that your IDataObject will work under all conditions it is wise to provide a full implementation of this interface.

IEnumFORMATETC Methods | Description
Next | Return the next FORMATETC structure in the enumeration.
Skip | Skip the specified number of FORMATETC structures (i.e. don't return them).
Reset | Return the enumeration to the beginning.
Clone | Return an identical IEnumFORMATETC interface to the current one, with the exact same underlying state.

The diagram below should help to illustrate the concept of the IEnumFORMATETC interface.

The enumeration contains three items, with the "enumeration index" initially starting at the first item (index zero).

  1. The Next method is called to return the first FORMATETC structure at index zero, and as a side effect advances the enumerator to index 1.
  2. The Skip method is called (with an argument of 2, skipping two positions), advancing to the end of the enumeration (index 3).
  3. The Reset method is called to return the index back to the start (index zero).

The IEnumFORMATETC is actually very simple as there are only four methods to implement:

?
class CEnumFormatEtc : public IEnumFORMATETC
{
public:
    //
    // IUnknown members
    //
    HRESULT __stdcall  QueryInterface (REFIID iid, void ** ppvObject);
    ULONG __stdcall  AddRef (void);
    ULONG __stdcall  Release (void);
    //
    // IEnumFormatEtc members
    //
    HRESULT __stdcall  Next  (ULONG celt, FORMATETC * rgelt, ULONG * pceltFetched);
    HRESULT __stdcall  Skip  (ULONG celt);
    HRESULT __stdcall  Reset (void);
    HRESULT __stdcall  Clone (IEnumFORMATETC ** ppEnumFormatEtc);
    //
    // Construction / Destruction
    //
    CEnumFormatEtc(FORMATETC *pFormatEtc, int nNumFormats);
    ~CEnumFormatEtc();
private:
    LONG m_lRefCount;        // Reference count for this COM interface
    ULONG m_nIndex;           // current enumerator index
    ULONG m_nNumFormats;      // number of FORMATETC members
    FORMATETC * m_pFormatEtc;       // array of FORMATETC objects
};

Constructing an IEnumFORMATETC object

The most complex aspect of the IEnumFORMATETC is creating the object, and implementing the COM methods is really very simple after this. Well, creating one is very easy, because all we need to do is use the C++ operator new to do this:

?
IEnumFORMATETC *pEnumFormatEtc = new CEnumFormatEtc( fmtetc, numfmts );
?
CEnumFormatEtc::CFormatEtc(FORMATETC *pFormatEtc, int nNumFormats)
{
    m_lRefCount   = 1;
    m_nIndex      = 0;
    m_nNumFormats = nNumFormats;
    m_pFormatEtc  = new FORMATETC[nNumFormats];
    // make a new copy of each FORMATETC structure
    for(int i = 0; i < nNumFormats; i++)
    {
        DeepCopyFormatEtc(&m_pFormatEtc[i], &pFormatEtc[i]);
    }
}

Let's break down what this C++ constructor does. It takes two arguments - a pointer to an array of FORMATETC structures, and an integer specifying how many items there are in the array.

The first line initializes the object's reference count - this is standard across all COM objects and we should be pretty familiar with this, so I won't cover this any further.

The next set of tasks involves initializing the enumeration's state. The member variable m_nIndex represents the current position within the enumeration, so it is natural for this to start at zero. Likewise, the m_nNumFormats variable is used to represent the end of the enumeration. With just these two variables we can keep track of the enumeration's current position and ending position.

The most important step is to allocate a new copy of the FORMATETC array that was passed in as an argument. An array is allocated (m_pFormatEtc) which will hold all the structures that will be enumerated. Each enumeration needs to have it's own private "cache" of FORMATETC structures. The key detail is the way that the FORMATETC structures are copied - here, a new function has been introduced called DeepCopyFormatEtc.

?
void DeepCopyFormatEtc(FORMATETC *dest, FORMATETC *source)
{
    // copy the source FORMATETC into dest
    *dest = *source;
    if(source->ptd)
    {
        // allocate memory for the DVTARGETDEVICE if necessary
        dest->ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE));
        // copy the contents of the source DVTARGETDEVICE into dest->ptd
        *(dest->ptd) = *(source->ptd);
    }
}

The first line of this function is deceptively simple:

?
*dest = *source;

This is just shorthand "C" notation for a standard memcpy. In fact, this is almost all that is required because it correctly performs a binary copy of the contents of one FORMATETC structure to another. The problem arises when the source FORMATETC::ptd member has been initialized to point to a DVTARGETDEVICE structure.

Just performing a memcpy of the FORMATETC's is not enough, because both FORMATETC structure point to the original DVTARGETDEVICE. It is therefore necessary to make our own private copy of this structure.

The documentation for IEnumFORMATETC::Next states that the caller must use CoTaskMemFree API to free the DVTARGETDEVICE structure. This logically implies that the structure must have first been allocated using CoTaskMemAlloc, so this is what the DeepCopy function does - allocates a new DVTARGETDEVICE structure using CoTaskMemAlloc and sets dest->ptd to point to it instead of the original one. Then the source->DVTARGETDEVICE structure is copied across to the new one.

Cleaning up an IEnumFORMATETC object

The C++ destructor for the CEnumFormatEtc class must cleanup any memory allocation that was performed in the constructor.

?
CEnumFormatEtc::~CEnumFormatEtc()
{
    // first free any DVTARGETDEVICE structures
    for(ULONG i = 0; i < m_nNumFormats; i++)
    {
        if(m_pFormatEtc[i].ptd)
            CoTaskMemFree(m_pFormatEtc[i].ptd);
    }
    // now free the main array
    delete[] m_pFormatEtc;
}

This is basically a simple task of calling CoTaskMemFree to deallocate any of the DVTARGETDEVICE structures that were allocated in the constructor. Once these have been freed, the main m_pFormatEtc array is deallocated.

Replacing SHCreateStdEnumFmtEtc

You may be wondering why we are bothering with this tutorial at all, because the SHCreateStdEnumFmtEtc API call can be used to create a full instantiation of the IEnumFORMATETC interface:

?
HRESULT SHCreateStdEnumFmtEtc(UINT cfmt, const FORMATETC afmt[], IEnumFORMATETC **ppenumFormatEtc);

Unfortunately this API call only exists on Windows 2000 and above, so unless you are prepared to drop support for any older version of Windows, we still have to implement IEnumFORMATETC. What we will do though, is write a drop-in replacement version of SHCreateStdEnumFmtEtc which we can easily switch from once we decide to support only Windows 2000. Our version will look like this:

?
HRESULT CreateEnumFormatEtc(UINT cfmt, FORMATETC *afmt, IEnumFORMATETC **ppEnumFormatEtc)
{
    if(cfmt == 0 || afmt == 0 || ppEnumFormatEtc == 0)
        return E_INVALIDARG;
    *ppEnumFormatEtc = new CEnumFormatEtc(afmt, cfmt);
    return (*ppEnumFormatEtc) ? S_OK : E_OUTOFMEMORY;
}

The function is very simple because all the hard work has been done in the CEnumFormatEtc constructor. All we need to do is create a new instance of the class (using the new operator) and return it in the pointer specified as the last parameter. The rest of the code is simply error checking.

Using this API is really simple:

?
FORMATETC fmtetc = { CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
IEnumFORMATETC *pEnumFormatEtc;
CreateEnumFormatEtc(1, &fmtetc, &pEnumFormatEtc);

This may seem to be alot of work just to enumerate some simple FORMATETC structures, but it is worth it because our COM enumerator will now be truly stand-alone, and the rest of the interface is now very simple to implement.

IEnumFORMATETC::Reset

The purpose of this member is really simple - start the enumeration again from the beginning.

?
HRESULT CEnumFormatEtc::Reset(void)
{
    m_nIndex = 0;
    return S_OK;
}

The implementation above should be self-explanatory.

IEnumFORMATETC::Skip

Again the implementation is so straight-forward practically no explanation is required.

?
HRESULT CEnumFormatEtc::Skip(ULONG celt)
{
    m_nIndex += celt;
    return (m_nIndex <= m_nNumFormats) ? S_OK : S_FALSE;
}

The function merely advances the enumeration by the specified number of units. Note that although no attempt is made to keep the index within the range of the enumeration, the error value returned does indicate whether or not the enumeration has been advanced too far.

IEnumFORMATETC::Clone

The clone function may seem a little mysterious at first. Although I have very rarely seen this function called it is quite simple to implement so it doesn't hurt to do it anyway.

?
HRESULT CEnumFormatEtc::Clone(IEnumFORMATETC **ppEnumFormatEtc)
{
    HRESULT hResult;
    // make a duplicate enumerator
    hResult = CreateEnumFormatEtc(m_nNumFormats, m_pFormatEtc, ppEnumFormatEtc);
    if(hResult == S_OK)
    {
        // manually set the index state
        ((CEnumFormatEtc *)*ppEnumFormatEtc)->m_nIndex = m_nIndex;
    }
    return hResult;
}

The code above simply creates a new instance of the IEnumFORMATETC interface, using the CreateEnumFormatEtc function we wrote earlier. The current enumeration's internal state is used, so the effect is a duplicate interface with the same internal state.

The complicated looking cast inside the "if-clause" is used to preserve the index position of the enumeration. The cast is necessary because the IEnumFORMATETC interface has no accessible internal variables. However, we know that the ppEnumFormatEtc is really a CEnumFormatEtc, so this is a safe cast to perform. The cast operation looks more complicated than it is because we also have to dereference the ppEnumFormatEtc parameter, in order to access the pointer-to-IEnumFORMATETC that was stored there.

IEnumFORMATETC::Next

The Next member function is a little more involved than the others.

?
HRESULT CEnumFormatEtc::Next(ULONG celt, FORMATETC *pFormatEtc, ULONG *pceltFetched)
{
    ULONG copied = 0;
    // copy the FORMATETC structures into the caller's buffer
    while(m_nIndex < m_nNumFormats && copied < celt)
    {
        DeepCopyFormatEtc(&pFormatEtc[copied], &m_pFormatEtc[m_nIndex]);
        copied++;
        m_nIndex++;
    }
    // store result
    if(pceltFetched != 0)
        *pceltFetched = copied;
    // did we copy all that was requested?
    return (copied == celt) ? S_OK : S_FALSE;
}

The function looks quite complicated but can be broken down into three major operations. The major portion of the code is the while-loop which is responsible for copying FORMATETC structures (using the deep-copy routine). The loop is structured in such a way that only "in-range" elements are copied into the supplied buffer.

The second part of the code returns the actual number of items copied, and returns an error value indicating whether or not all the requested items were copied.

The final section merely returns an error value indicating success or failure to copy the requested number of items.

Coming up in Part 5 - IDropSource

Well that's it for the IEnumFORMATETC interface. What we have now is a complete, stand-alone implementation, and a very handy CreateEnumFormatEtc API to easily create enumerator interfaces.

The next part of this tutorial series will be the IDropSource interface. It's been a long time coming but hopefully it will be worth the wait!

Don't forget to check out the IDataObject Viewer utility also!

‹ Implementing IDataObject up Drop Source ›

16 Jun 2004

Posted in Tutorials

win32

OLE Drag and Drop

  • Drag and Drop Introduction
  • OLE Data Transfers
  • Implementing IDataObject
  • Enumerating FORMATETC
  • Drop Source
  • Drop Target
Powered by Drupal


Drop Target | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
droptarget.zip

Published on 22 Jan 2012

7.75 Kb

Download
Catch22OLEpart6.rar

Published on 22 Jan 2012

7.53 Kb

Drop Target

OLE Drag and Drop

Updated 6 Dec 2006

Many thanks to Davide Chiodi from Italy who has very kindly converted the drop-target code into a Pure C implementation - download now!

Welcome to the sixth part of the "OLE Drag and Drop" tutorial series! This article will concentrate on implementing a small application which will act as a drop-target. What this means is that our application will be capable of receiving objects (be they files, pictures or text) which are dragged onto it.

We will implement an IDropTarget COM interface which will allow any OLE application to drag it's data over our application. This will take the form of a simple EDIT control which can act as a target for dropped CF_TEXT data. Hopefully you will be able to take the code presented here and "drag" it straight into your own apps ;-)

Become a "Drop Target"

In order for a window to accept data from a drap-drop operation, it must be registered as a "drop target". There is an OLE API call - RegisterDragDrop - which is used for this very purpose. The function prototype looks like this:

?
WINOLEAPI RegisterDragDrop(HWND hwnd, IDropTarget * pDropTarget);

The first parameter to this function is the window handle, of the window that is destined to be a drop target. The second parameter is a pointer to the IDropTarget COM object. The COM/OLE runtime will call the methods on this interface during the course of a drag-drop operation.

Likewise there is an OLE API call to remove drag-and-drop functionality from a window:

?
WINOLEAPI RevokeDragDrop(HWND hwnd);

All that is required from us is to called RegisterDragDrop when our window is created, and RevokeDragDrop when our window is destroyed. Before we can call RegisterDragDrop though, we need to construct a COM object which supports the IDropTarget interface.

The IDropTarget Interface

The IDropTarget Interface is relatively simple, with only four functions that need to be implemented. Of course there is also the IUnknown interface which needs to be implemented but we have already covered that earlier.

IDropTarget Methods | Description
DragEnter | Determines whether a drop can be accepted and its effect if it is accepted.
DragOver | Provides target feedback to the user through the DoDragDrop function.
DragLeave | Causes the drop target to suspend its feedback actions.
Drop | Drops the data into the target window.

Each one of these four functions will be called by the COM/OLE runtime whenever an "object" is dragged over our registered window. Each function has a different task, as shown in the table above. It is up to us to provide the implementations of these functions.

Implementing IDropTarget

The IDropTarget interface is (in my experience) very difficult to write without using "application specific" code. i.e. there is no easy way to make a generic IDropTarget COM object which can be re-used between all of your applications.

This is because the requirement of IDropTarget is to show graphical feedback in your target window whenever an object is dragged over it, and also the application-specific code to access the data object's content.

Out of all the drag+drop interfaces, the IDropTarget is the one that would be best integrated directly into your window class. For example, supposing you have implemented a custom window using a C++ class - the best method to add drop-target support to this window is have your custom-window class inherit directly from IDropTarget, rather than having a separate CDropTarget class. This means that your drop-target code would have full access to all of your internal window state.

However, for the time-being here is the CDropTarget class in all it's glory:

?
class CDropTarget : public IDropTarget
{
public:
    // IUnknown implementation
    HRESULT __stdcall QueryInterface (REFIID iid, void ** ppvObject);
    ULONG __stdcall AddRef (void);
    ULONG __stdcall Release (void);
    // IDropTarget implementation
    HRESULT __stdcall DragEnter(IDataObject * pDataObject, DWORD grfKeyState, POINTL pt, DWORD * pdwEffect);
    HRESULT __stdcall DragOver(DWORD grfKeyState, POINTL pt, DWORD * pdwEffect);
    HRESULT __stdcall DragLeave(void);
    HRESULT __stdcall Drop(IDataObject * pDataObject, DWORD grfKeyState, POINTL pt, DWORD * pdwEffect);
    // Constructor
    CDropTarget(HWND hwnd);
    ~CDropTarget();
private:
    // internal helper function
    DWORD DropEffect(DWORD grfKeyState, POINTL pt, DWORD dwAllowed);
    bool QueryDataObject(IDataObject *pDataObject);
    // Private member variables
    long m_lRefCount;
    HWND m_hWnd;
    bool m_fAllowDrop;
    // Other internal window members

};

As well as the reference count, we need to store two additional variables: The m_hWnd variable is the window handle of the drop-target is needed so we can provide visual feedback during the drag-drop operation. The m_fAllowDrop is used to indicate whether or not the dataobject being dropped on us contains useful data. This is so we don't have to continually query the dataobject - basically its an optimization trick.

IDropTarget::DragEnter

Let's look at the IDropTarget::DragEnter function first of all, because this is the first function that is called by COM when an object is dragged over our window:

?
HRESULT DragEnter(
   IDataObject * pDataObject,    // Pointer to the interface of the source data object
   DWORD grfKeyState,    // Current state of keyboard modifier keys
   POINTL        pt,             // Current cursor coordinates
   DWORD *       pdwEffect       // Pointer to the effect of the drag-and-drop operation
   );

Look closely at the function prototype above, because it is important to understand what each of the parameters are used for.

  • IDataObject - the very first argument is another pointer to the data object passed to us (via COM) by the source of the drag-drop operation. The IDataObject is simply the "transport medium" for the data that is being dropped. We can query the data object during DragEnter to see if it has any data that we want.
  • gfrKeyState - holds the state of the keyboard modifier keys such as Control, Alt and Shift, and the state of the mouse buttons. It's a simple DWORD variable comprised using one or more of the following bit-flags: MK_CONTROL, MK_SHIFT, MK_ALT, MK_BUTTON, MK_LBUTTON etc.
  • pt - a POINTL structure, containing the coordinates of the mouse as it enters our window. In some applications this parameter would be used to check if the mouse was positioned over allowable drop areas, or used simply to position some kind of "insertion" cursor to indicate where the dropped data would go.
  • pdwEffect - pointer to a DWORD value that specifies the drop-effects that are allowed by the drop-source. This value is the same as the dwOKEffect value specified by the caller of DoDragDrop.

Our implementation of DragEnter needs to perform several common tasks, in addition to drawing graphical feedback.

  1. Inspect the supplied data object and decide if it contains any useful data or not.
  2. Inspect the keyboard state stored in grfKeyState and calculate what the drop-effect should be. i.e. if the Control key is held down, the drop-effect should be "copy", if Shift is held down, the drop-effect should be "move".
  3. Verify that the computed drop-effect is compatible with those allowed by the drop-source.
  4. Store the final drop-effect in the DWORD pointed to by pdwEffect.

Don't get caught up in the complexity of all this. The purpose of DragEnter is to simply say "yes or no" to the drag-drop operation, and to specify what the drop-effect should be so that the mouse-cursor can be updated by OLE.

?
HRESULT __stdcall CDropTarget::DragEnter(IDataObject *pDataObject, DWORD grfKeyState,
                                              POINTL pt, DWORD *pdwEffect)
{
    // does the dataobject contain data we want?
    m_fAllowDrop = QueryDataObject(grfKeyState, pdwEffect, pDataObject);

    if(m_fAllowDrop)
    {
        // get the dropeffect based on keyboard state
        *pdwEffect = DropEffect(grfKeyState, pt, *pdwEffect);
        SetFocus(m_hWnd);
        PositionCursor(m_hWnd, pt);
    }
    else
    {
        *pdwEffect = DROPEFFECT_NONE;
    }    return S_OK;
}

Apart from setting focus to the underlying window and positioning the EDIT caret on the nearest character under the mouse, the DragEnter function has been simplified by delegating the functionality to two internal helper routines:

?
bool CDropTarget::QueryDataObject(IDataObject *pDataObject)
{
    FORMATETC fmtetc = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    // does the data object support CF_TEXT using a HGLOBAL?
    return pDataObject->QueryGetData(&fmtetc) == S_OK ? true : false;
}

QueryDataObject is a private member function which is used purely to inspect the supplied data object, and decide if it contains data that is meaningful to our drop-target. In our case, we only accept CF_TEXT data stored as a HGLOBAL, so this is what we ask for. A private member variable m_fAllowDrop is used to remember this decision.

?
DWORD CDropTarget::DropEffect(DWORD grfKeyState, POINTL pt, DWORD dwAllowed)
{
    DWORD dwEffect = 0;
    // 1. check "pt" -> do we allow a drop at the specified coordinates?
    // 2. work out that the drop-effect should be based on grfKeyState
    if(grfKeyState & MK_CONTROL)
    {
        dwEffect = dwAllowed & DROPEFFECT_COPY;
    }
    else if(grfKeyState & MK_SHIFT)
    {
        dwEffect = dwAllowed & DROPEFFECT_MOVE;
    }
    // 3. no key-modifiers were specified (or drop effect not allowed), so
    //    base the effect on those allowed by the dropsource
    if(dwEffect == 0)
    {
        if(dwAllowed & DROPEFFECT_COPY) dwEffect = DROPEFFECT_COPY;
        if(dwAllowed & DROPEFFECT_MOVE) dwEffect = DROPEFFECT_MOVE;
    }
    return dwEffect;
}

The DropEffect helper function is used to compute the drop-effect based on the keyboard state and the effects allowed by the source.

First of all the grfKeyState variable is checked to see if either the Control or Shift keys are being used. The standard OLE behaviours for these keys are that Control should force a Copy of data, and Shift should force a Move of data. If both are held down, the the data should be Linked (i.e. the source should make a shortcut to the target), but we don't support this feature.

The important thing to note is the use of the "bitwise-AND" operator when assigning the drop-effect to dwEffect:

?
dwEffect = dwAllowed & DROPEFFECT_COPY;

The result of this assignment is simple - dwEffect will be assigned the value DROPEFFECT_COPY, but only if this value is present in the dwAllowed variable. This use of logic prevents us from forcing a drop-effect that is not allowed by the source.

The next stage is to decide what to do if no keyboard modifiers are present - i.e. Control or Shift are not in use. In this case we simply inspect the drop-effects allowed by the source and choose (in order of priority) which one to use - in our implementation, we let data moves override data copies.

IDropTarget::DragOver

The DragOver function will be called multiple times during the lifetime of a drag-drop operation. Therefore it is important for this function to be efficiently written. DragOver is called whenever the state of the keyboard modifiers change (i.e. shift/control etc), or when the mouse moves. It is the responsibilty of this function to indicate to OLE what the drop-effect will be based on the state of the keyboard and mouse position.

?
HRESULT __stdcall CDropTarget::DragOver(DWORD grfKeyState, POINTL pt, DWORD * pdwEffect)
{
    if(m_fAllowDrop)
    {
        *pdwEffect = DropEffect(grfKeyState, pt, *pdwEffect);
        PositionCursor(m_hWnd, pt);
    }
    else
    {
        *pdwEffect = DROPEFFECT_NONE;
    }
    return S_OK;
}

DragOver is very simple to write, because the logic is identical to that of DragEnter. We use the previously computed m_fAllowDrop and the DropEffect helper routine to return a drop-effect through the pdwEffect pointer.

IDropTarget::DragLeave

The DragLeave function is called whenever the mouse cursor is moved outside of our drop-target window, or the Escape key is pressed which cancels the drag-drop operation. It's prototype (and implementation) is really simple:

?
HRESULT __stdcall CDropTarget::DragLeave(void)
{
    return S_OK;
}

This is the most basic way to write this function. The only reason this function exists is so that applications that make heavy use of graphical feedback effects get a chance to clean up once the mouse moves out of the window. For example, imagine the following scenario: whenever something is dragged over a drop-target, the DragEnter function is used to change the colour of the window-border. In this case, the DragLeave function would be used to restore the window-border.

IDropTarget::Drop

The Drop function's prototype is exactly the same as the DragEnter function:

?
HRESULT __stdcall CDropTarget::Drop(IDataObject *pDataObject, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
    PositionCursor(m_hWnd, pt);
    if(m_fAllowDrop)
    {
        DropData(m_hWnd, pDataObject);
        *pdwEffect = DropEffect(grfKeyState, pt, *pdwEffect);
    }
    else
    {
        *pdwEffect = DROPEFFECT_NONE;
    }
    return S_OK;
}

This function is called when OLE has determined that the drag-drop will go ahead. We get the same interface pointer to the IDataObject that we received during DragEnter, which we can now retrieve data from to paste into our edit window.

The DropData helper function is used to access the CF_TEXT data inside the dataobject and insert it into the edit control. This routine is purely academic and as we already know how to access a dataobject I won't bother detailing it any further - just look at the sourcecode download if you are interested.

Conclusion

We've done it! It's taken six tutorials to get to this stage, but it was necessary to break up the subject matter into managable chunks.

So what have we accomplished?

At this stage we know how to implement IDataObject, IEnumFORMATETC, IDropTarget and IDropSource, as well as access the Windows clipboard using the new OLE functions.

There is still scope for further tutorials though. The next tutorial (or two) will look at dragging and dropping files (and filenames), and also using the IStream COM interface to stream file content between applications and the Windows Shell.

As always, I'd like to hear any feedback you may have on this tutorial series. More feedback equals more tutorials, so stay tuned!

‹ Drop Source up

17 Jun 2004

Posted in Tutorials

win32

OLE Drag and Drop

  • Drag and Drop Introduction
  • OLE Data Transfers
  • Implementing IDataObject
  • Enumerating FORMATETC
  • Drop Source
  • Drop Target
Powered by Drupal


Drop Source | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
dropsource.zip

Published on 22 Jan 2012

13.41 Kb

Download
Catch22OLEpart5.rar

Published on 22 Jan 2012

10.78 Kb

Drop Source

OLE Drag and Drop

Updated 6 Dec 2006

Many thanks to Davide Chiodi from Italy who has very kindly converted the drop-source code into a Pure C implementation

Welcome to the fifth article in the "OLE Drag and Drop" tutorial series! We are almost at the final stages in our OLE drag and drop implementation. The only thing left to do is implement the IDropSource and IDropTarget interfaces. Once we have done this we will be ready to add drag and drop to any application..

The aim of this article is to implement a simple application which can be used as the source of a drag-drop operation. It won't be able to accept any drag-drop data, but this doesn't matter because we can use any normal Windows program which supports text drag+drop (such as WordPad) for testing. The application will basically be a normal Windows EDIT control, which will be subclassed and drag-support added.

The details of this subclassing won't be discussed in this tutorial, but the source-code download clearly demonstrates how to perform this simple task.

Become a "Drop Source"

It is very simple to initiate a drag-and-drop operation. Calling the DoDragDrop API call is sufficient.

?
WINOLEAPI DoDragDrop(
   IDataObject  * pDataObject,   // Pointer to the data object
   IDropSource  * pDropSource,   // Pointer to the source
   DWORD dwOKEffect,    // Effects allowed by the source
   DWORD * pdwEffect      // Pointer to effects on the source
   );

As soon as this API is called, the OLE runtime takes over and handles all the necessary mouse and keyboard windows messages on behalf of your application, so you basically release control to OLE once you call this function.

The first two parameters to DoDragDrop are COM interfaces. One is an IDataObject - we have already implemented this in a previous tutorial.

The third parameter is a DWORD value which specifies (in the form of a bit-mask) the "effects" that are allowed by the source (i.e. us). These effects are taken from the DROPEFFECT_xxx values, and will usually be and combination of DROPEFFECT_MOVE and DROPEFFECT_COPY. If we wanted to allow only copying data from our source, then we would specify just DROPEFFECT_COPY, and the opposite is true as well.

The last parameter is a pointer to a DWORD value. This value is accessed when DoDragDrop returns, and will contain the "effect" or action that OLE wants the source to perform - i.e. did the user elect to move or copy the data?

The code to perform a drag-and-drop operation is usually split into three steps. First of all though we need to write a small utility function called StringToHandle, which will convert a normal char* string into HGLOBAL form so we can use it with OLE:

?
HANDLE StringToHandle(char *szText, int nTextLen)
{
    void *ptr;

    // if text length is -1 then treat as a nul-terminated string
    if(nTextLen == -1)
        nTextLen = lstrlen(szText);

    // allocate and lock a global memory buffer. Make it fixed
    // data so we don't have to use GlobalLock
    ptr = (void *)GlobalAlloc(GMEM_FIXED, nTextLen + 1);

    // copy the string into the buffer
    memcpy(ptr, szText, nTextLen);
    ptr[nTextLen] = '\0';

    return ptr;
}

The StringToHandle function has absolutely no error checking so it's up to you to add this on your own. The next step is to prepare some data to use in the drag-and-drop operation:

?
FORMATETC fmtetc =
{
    CF_TEXT,            // we will be dropping some text
    0,
    DVASPECT_CONTENT,
    -1,
    TYMED_HGLOBAL       // stored as a HGLOBAL
};
STGMEDIUM stgmed =
{
    TYMED_HGLOBAL,      // the data goes here
    { 0 },
    0
};
// Create a HGLOBAL inside the storage medium
stgmed.hGlobal = StringToHandle("Hello, World", -1);

Next up is the creation of the two COM interfaces required for drag and drop - IDropSource and IDataObject. We have already implemented CreateDataObject in a previous tutorial, and CreateDropSource will be implemented shortly!

?
IDropSource *pDropSource;
IDataObject *pDataObject;
CreateDropSource(&pDropSource);
CreateDataObject(&pDataObject, &fmtetc, &stgmed, 1);

The call to DoDragDrop can be made once the IDataObject and IDropSource have been successfully created.

?
DWORD dwResult;
// do the drag-drop!
dwResult = DoDragDrop(pDataObject, pDropSource, DROPEFFECT_COPY, &dwEffect);
// finished. Check the return values to see if we need to do anything else
if(dwResult == DRAGDROP_S_DROP)
{
    if(dwEffect == DROPEFFECT_MOVE)
    {
        // remove the data we just dropped from active document
    }
}

The very last thing to do is cleanup any resources we used. First of all we remove the last reference to the two COM interfaces, after which they will be automatically deleted. And lastly, we delete the HGLOBAL memory buffer that contains our text.

?
// release the COM interfaces
pDropSource->Release();
pDataObject->Release();
ReleaseStgMedium(&stgmed);

When to call DoDragDrop

Knowing how to initiate a drag-and-drop operation is all very well, but it is important to understand where to integrate the above code into your applications.

Because drag & drop is mouse-based, it is customary for an application to initiate it whilst processing normal Windows mouse messages. If you take the time to play with some drag-and-drop enabled applications (such as WordPad) you will observe the following behaviour for the RichEdit control:

  1. When the mouse moves over a selected area of text it's cursor shape changes to an arrow.
  2. When the left button is pressed, the selection is not removed. Instead an internal state is set to indicate that a drag-drop operation might be about to start.
  3. When the mouse is first moved (and the internal state indicates that the left button is currently being held down inside a selected area of text), the drag and drop operation starts.
  4. At this point, OLE takes over and handles all further mouse messages until the operation is complete.
  5. However, if the left button is released and the mouse didn't move at all, it is customary for the RichEdit's selection to be cleared and the text-caret positioned under the mouse.

This behaviour is quite simple to implement in C or C++. For our subclassed EDIT control it will look something like this:

?
case WM_LBUTTONDOWN:
    if(MouseInSelection(hwndEdit))
    {
        fMouseDown = TRUE;
        return 0;
    }
    break;
case WM_MOUSEMOUVE:
    if(fMouseDown == TRUE)
    {
        DoDragDrop(...);
    }
    fMouseDown = FALSE;
    break;
case WM_LBUTTONUP:
    fMouseDown = FALSE;
    break;

The IDropSource Interface

IDropSource is the simplest of all the drag-and-drop interfaces. Excluding the IUnknown functions, it has only two functions that need to implemented.

IDropSource Methods | Description
QueryContinueDrag | Determines whether a drag-and-drop operation should be continued, cancelled or completed, based on the state of the mouse buttons and the <Escape>, <Control> and <Shift> keys.
GiveFeedback | Provides a method for the source of the drag-drop to give visual feedback based on the state of the modifier keys listed above (i.e. mouse buttons, escape, control etc).

Both of these functions are called by the COM/OLE runtime whenever the state of the drag-and-drop modifier keys are changed. Very little work needs to be done to implement this interface - in fact, far more coding has already been performed just to prepare for the drag-drop!

Implementing IDropSource

Again we can use a single source-file to implement a drop-source. Inside dropsource.cpp will be the class declaration for our drop-source object. This is what you need to type in:

?
class CDropSource : public IDropSource
{
public:
    //
    // IUnknown members
    //
    HRESULT __stdcall QueryInterface    (REFIID iid, void ** ppvObject);
    ULONG __stdcall AddRef            (void);
    ULONG __stdcall Release           (void);
    //
    // IDropSource members
    //
    HRESULT __stdcall QueryContinueDrag (BOOL fEscapePressed, DWORD grfKeyState);
    HRESULT __stdcall GiveFeedback      (DWORD dwEffect);
    //
    // Constructor / Destructor
    //
    CDropSource();
    ~CDropSource();
private:
    //
    // private members and functions
    //
    LONG m_lRefCount;
};

The constructor for this class does not perform any task other than initializing the object's reference count.

IDropSource::QueryContinueDrag

Below is the definition for the IDropSource::QueryContinueDrag function:

?
HRESULT QueryContinueDrag(
   BOOL fEscapePressed,        // Is the <Escape> key being pressed?
   DWORD grfKeyState,           // Current state of keyboard modifier keys
   );

This function can return one of three values:

  • S_OK The drag operation should continue. This result occurs if no errors are detected, the mouse button starting the drag-and-drop operation has not been released, and the Esc key has not been detected.
  • DRAGDROP_S_DROP The drop operation should occur completing the drag operation. This result occurs if grfKeyState indicates that the key that started the drag-and-drop operation has been released.
  • DRAGDROP_S_CANCEL The drag operation should be canceled with no drop operation occurring. This result occurs if fEscapePressed is TRUE, indicating the Esc key has been pressed.

Is is customary in COM for the following two behaviours to be observed whilst in a drag-and-drop operation.

  1. When the Escape key is pressed, cancel the drag-drop operation.
  2. When the Left mouse button is released, the drop should be performed.

Adhering to these guidelines results in the following implementation of IDropSource::QueryContinueDrag:

?
HRESULT __stdcall CDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState)
{
    // if the Escape key has been pressed since the last call, cancel the drop
    if(fEscapePressed == TRUE)
        return DRAGDROP_S_CANCEL;
    // if the LeftMouse button has been released, then do the drop!
    if((grfKeyState & MK_LBUTTON) == 0)
        return DRAGDROP_S_DROP;
    // continue with the drag-drop
    return S_OK;
}

IDropSource::GiveFeedback

The IDropSource::GiveFeedback function is usually different for every application, because no one application will be the same. However, unless we are providing graphical feedback effects whilst an object is being dragged from our application, our implementation of GiveFeedback is extremely simple.

?
HRESULT __stdcall CDropSource::GiveFeedback(DWORD dwEffect)
{
    return DRAGDROP_S_USEDEFAULTCURSORS;
}

The dwEffect parameter (which tells us which mouse buttons are pressed and which of the keyboard modifiers are in use) can be ignored for alot of drag-and-drop applications. By simply returning the value DRAGDROP_S_USEDEFAULTCURSORS we can instruct COM to update the mouse cursor automatically whenever the modifiers change.

Of course, we could inspect the DROPEFFECT_xxx flags inside dwEffect, do some painting on our source-window and return S_OK instead, but why bother?

Coming up in Part 6 - IDropTarget

With the drop-source out of the way, we are now ready to start implementing a drop target interface. This is the last component needed for OLE drag and drop.

‹ Enumerating FORMATETC up Drop Target ›

17 Jun 2004

Posted in Tutorials

win32

OLE Drag and Drop

  • Drag and Drop Introduction
  • OLE Data Transfers
  • Implementing IDataObject
  • Enumerating FORMATETC
  • Drop Source
  • Drop Target
Powered by Drupal


Design & Implementation of a Win32 Text Editor | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Design & Implementation of a Win32 Text Editor

Neatpad Text Editor

Welcome to the brand new tutorial series - "Design and Implementation of a Win32 Text Editor!"

The purpose of these tutorials is to follow the development of a win32 text editor - codenamed Neatpad. Each tutorial will take you step-by-step over the major components and design decisions that lay ahead.

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Neatpad Overview ›

28 Jan 2005

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Neatpad Overview | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
neatpad1.zip

Published on 22 Jan 2012

37.53 Kb

Download

Neatpad Overview

Design & Implementation of a Win32 Text Editor

Introduction

Welcome to the brand new tutorial series - "Design and Implementation of a Win32 Text Editor!" The purpose of these tutorials is to follow the development of a win32 text editor - codenamed Neatpad. Each tutorial will take you step-by-step over the major components and design decisions that lay ahead.

Now, whilst the title says "text-editor", this is not the real goal of this tutorial series. Rather, we will aim to write a complete edit control which will be able to provide the core editing front-end for a text editor. Of course, we will have to build a small test application (Neatpad) to test the code with, but we will not go any further than the standard Windows Notepad goes.

Alternative Win32 Solutions

Before we begin you should also check out the following three win32 open-source editor components:

Scintilla, Brainchild and CrystalEdit.

Scintilla in particular looks very nice (it has loads of good features) however all three editors suffer from a large, unwieldy code-base, and all have limitations in their memory-management schemes meaning that large files are not handled particularly smartly.

Text Editor Resources

The following set of resources discuss text editor design rather than individual applications, so you may find it a useful exercise to look through the links and read what's there.

  • Data Structures For Text Sequences
    Compares and describes many different text editor data structures. The author (Charles Crowley) has done a very good job, although no sample code is available. The one that stands out is the "Piece Table" method which is where the original idea for my HexEdit design originated. This will be the method we will use for our own text editor. The link above is quite old and is no longer accessible from the main page. Also the images are missing. However the complete PostScript version may be downloaded here. I also have a PDF version which can be downloaded directly.
  • The Craft of Text Editing
    Discusses issues related to the design of the EMACS text editor. Although a very well-known editor (primarily for it's user-interface) it appears to use a "buffer gap" method so presumably EMACS is not too good for larger files.
  • Design and Implementation of an Advanced Text Editor
    Whilst the text editor described is not advanced at all (calling it "basic" would be an overstatement), the document does contain some interesting ideas about text editors and user interfaces.
  • ned.rubyforge.org
    Contains a nice selection of links to other text-editing documents and websites.

Edit-control requirements

The very first thing we must do is to make sure that there aren't any text controls already available which will do the job perfectly well. I imagine that anyone who is reading this has come to the same conclusions that I have - the standard Windows edit and rich-edit controls are not up to scratch - they are slow, don't handle large files and have bad flicking problems for a start, and boy do I hate apps that flicker :-)

The primary reason I want to start this project is as a learning exercise - for me and for anyone else who reads this series. So now that we have decided to make our own edit control, the next step is to decide what features the edit control will have. This is important, because these initial design ideas can have big impacts on the final implementation. So, in no particular order, here are the features that I want an edit control to offer:

  • Unlimited file size and line length editing with little (or no) degradation in performance for larger files.
  • Fast, smooth graphic display. This includes text selection and smooth scrolling with no artifacts or glitches.
  • Syntax colouring. Come on, we're programmer's after all, so what good is an edit control that can't syntax-colour our source-code :-)
  • Full Clipboard and Drag-and-Drop support.
  • Single-font only. We will start by limiting this even further to fixed-width fonts, but with a view to handling variable width fonts if it doesn't look too difficult :-)
  • Complete Undo and Redo support.
  • ASCII, Unicode and UTF-8 compatible.

I'm sure that I'll dream up extra features as we move through this article series, but for now I think that'll be enough :-)

Choice of language

This is a tough one. Realistically speaking we have the choice between C and C++. C is good because everyone can compile it, whereas C++ has an advantage that is is easier to write this type of control using it. For this tutorial we are actually going to use a mixture of C and C++. The text editor control will be written using C++, but we will provide a "C" interface to the control, because our main application (Neatpad) will be written in "pure win32" C.

Don't expect this control to be available in .NET, or Visual Basic, or any other high-level language. Maybe towards the end of this article series we will look at putting the editor control in an external DLL so that other languages can use it, but for now this is a pure C/C++ solution with no 3rd party libraries.

Design of a Text Editor

A text editor is conceptually very simple. Just take a look at Notepad - all it has is a main window and an "edit" control inside this main window. This is the design we will follow when we are implementing our text editor.

The diagram below illustrates the key components to a text editor. First of all we have a top-level main window. This is the window that contains the title, menu and status bars. The main window doesn't know how to edit text files, it's only purpose is to provide an interface to the user. This component will be written in pure win32 C.

The most important component is the "TextView". This is a separate window that is a child of the main (parent) window. The TextView is the visual component of a text editor. This control's primary purpose is to display and edit text, but it needs to do other things besides this, such as display scrollbars, process mouse and keyboard input, support drag and drop etc.

The "TextDocument" component is not a visible one, but is used to store and manipulate a text file once it has been loaded into memory. The text object has no concept of windows, mice, drawing or painting. All it knows how to do is manipulate text, and supply text to the edit control when it wants to draw something.

The last component is not really part of a text editor, but plays an important role none-the-less. It is the disk-file that stores the text we want to edit. The TextDocument will interface directly with this text file, reading and writing to it when it is time to load or save a new document.

It is important to separate the TextView from the data it stores (the TextDocument). This has two major benefits. One is that we can attach multiple edit controls to one text object - this will enable us to add "split-views" sometime in the future. The biggest advantage is that we can change the way we store and represent a text file at any time without this impacting upon the design of the visual component.

One thing to note on the component design are the "interfaces" between the various components. The TextView is a line-oriented, graphical entity. Most of the work performed by the edit-window will be updating the display. This will entail retreiving data from the TextDocument on a line-by-line basis.

On the other end of the scale is the interface between the TextDocument and the disk-file. Rather than using a line-by-line strategy, the TextDocument will either load the entire file into memory in one go, or access the file in manageable chunks. No matter how this happens, we've got some interesting design decisions ahead :-)

Neatpad - a Win32 Text Editor

At the top of this tutorial there is a link to a zip-file, which contains the sourcecode to a skeleton text editor. I have called this project "Neatpad" in passing reference to the standard Windows Notepad.

When you unzip the download, there will be a single Visual C++ project workspace (neatpad.dsw) and two subdirectories (Neatpad and TextView). The Neatpad directory contains several files, the important ones listed below:

  • neatpad.c - the main Neatpad application source file.
  • resource.rc/h - Neatpad's resources.
  • neatpad.dsp - the Visual Studio project.

It is intended that you work using the main neatpad.dsw workspace in the top-level directory. From this workspace you can build the complete project (Neatpad and TextView). Although the workspace and projects are in Visual Studio 6.0 format, it shouldn't be difficult to convert the projects to other IDEs.

TextView - a Win32 Custom Control

The TextView directory contains the source-code to a skeleton win32 custom control, implemented using C++. The control does nothing apart from display "Hello World" in it's window - in fact most of the code is simply what is required to register and create the empty TextView window.

There are four files in the TextView directory - TextView.dsp, TextView.c, TextView.h and TextViewInternal.h:

  • TextView.h - the "public interface" to the TextView control.
  • TextView.c - the main implementation of the TextView window.
  • TextViewInternal.h - the private header file used by TextView.c, and contains the TextView C++ class definition.
  • TextView.dsp - the Visual Studio project.

Most of the future tutorials will concentrate on adding functionality to the TextView project.

Public Interface to the TextView

The basic method of controlling the TextView will be Windows Messages, using the SendMessage API, just like you would use with a standard Edit control. To achieve this we need to define a range of message values that we can use. These message values will be defined in the "public" TextView.h, so whenever you want to use the TextView control in your projects, simply #include TextView.h:

?
#define TEXTVIEW_CLASS   "TextView32"
#define TXM_BASE         (WM_USER)
#define TXM_OPENFILE     (TXM_BASE + 0)#define TextView_OpenFile(hwndTV, szFile) \
                  SendMessage((hwndTV), TXM_OPENFILE, 0, (LPARAM)(szFile))

As you can see only one message has been defined so far - TXM_OPENFILE. As we progress through this tutorial and add more functionality to the TextView, so we will add more messages as well. i imagine that our TextView will also support the standard EM_xxx edit control messages as well, so our TextView could be a simple drop-in replacement for that control.

The macro defined above (TextView_OpenFile) provides an alternative interface to the control. It is basically a wrapper around the SendMessage call and makes it easier to use. An example of opening a file is shown below:

?
TextView_OpenFile(hwndTextView, "C:\\SRC\\TEXTVIEW\\README.TXT");

Coming up in Part 2

This first part in the tutorial series was really just an introduction to what we are trying to achieve. I have assumed that the reader (you) as at least some knowledge of C and Win32 because you will need a reasonable level of programming experience if you intend to benefit from these tutorials. Please take some time to study the skeleton project download - the code is very simple but it is important that you understand how the various components are all going to plug together.

Future tutorials will begin to flesh out the functionality of the text-view control. As I've been experimenting with text editors I have drawn up a list of steps that I believe will be necessary to cover. The list below will hopefully give you an idea of the sequence of this tutorial series.

  • Drawing lines of text
  • Scrolling and scrollbars
  • Selection and highlighting using the mouse
  • Syntax colouring
  • Ascii, Unicode and UTF-8
  • Keyboard and Mouse input
  • Highlighting and text-selection.
  • Selection margin and borders.
  • Loading large files
  • Editing large files
  • Undo and Redo
  • Clipboard and Drag+Drop
  • Scrolling with the mouse
  • Searching for data
  • Folding editors

I will try and stick to the above topics as closely as possible, but bear in mind that I don't have a fixed project plan, so we will see this text editor evolve incrementally over the next few weeks.

‹ Design & Implementation of a Win32 Text Editor up Loading a text file ›

29 Jan 2005

Posted in Tutorials

win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Loading a text file | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
neatpad2.zip

Published on 22 Jan 2012

42.65 Kb

Download

Loading a text file

Design & Implementation of a Win32 Text Editor

Introduction

This is the second part of the "Design and Implementation of a Win32 Text Editor" article series. If you haven't already read part 1 then please do so now!

OK, so assuming you've downloaded, assimilated and compiled the source code that was made available, you should have a basic skeleton text editor which doesn't do anything yet. Our mission in part 2 is to load a text file into memory and display it in our TextView control. But let's not get carried away. The only aim right now is to load a text file and provide very basic display, we are nowhere near providing scrolling or keyboard and mouse support.

Text Documents

A Text Document is nothing more than a basic binary file, with the commonly understood convention that a text-file should not contain unprintable characters (i.e. ASCII control characters), and that lines of text are separated by a common end-of-line delimiter (such as a carriage-return / line-feed pair).

It is the task of a Text Editor to interpret a text-file's binary content and display this content in a line-oriented manner to the user. Part one of this tutorial series discussed the structure of a Text Editor - and described the TextView and TextDocument objects. The first thing we will concentrate on will therefore be the TextDocument object - which we will represent as a C++ class:

?
class TextDocument
{
public:
    bool init(char *filename);
    ULONG getline(ULONG lineno, char *buf, size_t len);
    ULONG linecount();
private:
    bool init_linebuffer();
    char *buffer;
    int length;
};

The basic C++ interface is very simple. We can load a file into the TextDocument using the init class member. We can retrieve a line of text using the getline method - where we specify a line number and a buffer into which to store the line contents.

Notice that the TextDocument class is entirely ASCII in operation at the moment - that is, there is no support for Unicode. We could have used C++ templates to support a variety of different types. However at this moment in time I am still undecided as to how best approach this problem, so we will leave the interface as simple as possible. After all, this is a "throw-away" implementation of TextDocument, and we will completely re-write it later on in the series.

Loading a text file

Our first attempt at loading a text file will try to be as simple as possible. The TextDocument::init function below is the main interface to the TextDocument:

?
bool TextDocument::init(char *filename)
{
    HANDLE hFile;
    hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
    if(hFile == INVALID_HANDLE_VALUE)
        return false;
    return init(hFile);
}

TextDocument::init simply opens a file using the standard win32 CreateFile API, and then passes control to a helper function which loads the file contents using the HANDLE returned by CreateFile:

?
bool TextDocument::init(HANDLE hFile)
{
    ULONG numread;
    if((length = GetFileSize(hFile, 0)) == 0)
        return false;
    // allocate new file-buffer
    if((buffer = new char[length]) == 0)
        return false;
    // read entire file into memory
    ReadFile(hFile, buffer, length, &numread, 0);
    // work out where each line of text starts
    init_linebuffer();
    CloseHandle(hFile);
    return true;
}

As you can see loading a file is very simple. We calculate how big the file is, allocate a buffer to hold the text, and then read the file into memory. This is not really a very smart thing to do, as large files will take a very long time to load, or may fail to load completely if there isn't enough memory available.

Please understand that this is a "throw-away" TextDocument class. Writing a TextDocument class which supports files of any size will be very difficult so I have deliberately kept this version of TextDocument as simple as possible. Later on in the series we will revisit file-loading and write this class properly.

Carriage-returns and Linefeeds

How many lines are there in a text document? How do we read a file line-by-line? The answer really depends on how you define what a line of text is. At it's simplest, a line of text is a sequence of characters within a file, with well-defined end of line marker. We don't really care what the characters are in each line, but we do need to know how to identify where the start and stop of each line is.

There are three main conventions for delimiting lines of text - under DOS and Windows, a carriage-return / line-feed pair is used. Under UNIX and Linux, a single line-feed character is used, and under Macintosh operating system, a single carriage-return is used. There are a number of issues which become apparent once we try to tackle all these cases, but for now we will just concentrate on the DOS/Windows case. A future tutorial will address the other last two options (and another case, where we can encounter a file with all combinations of line-separators).

The TextView control needs to know now many lines of text there are in it's document, because it must be able to setup the scrollbars to reflect the document length. We also need to be able to locate individual lines of text in a random order because we will be drawing the text document line-by-line.

The most common solution to this problem is to implement what is known as a "line buffer". Conceptually the line buffer is very simple - it is just an array of integer offsets which specify where each line of text in the document starts.

The diagram above illustrates a line-buffer on the left hand side. The buffer has been initialized with the block of text on the right - by processing the text, searching for carriage-return / line-feed sequences, and recording the offset for each line. Each array item (numbered 0-6) contains the offset of the character which starts each line of text.

The TextDocument will implement the line-buffer as it seems a natural choice to perform line-parsing in the same place as the file loading. The TextDocument::init_linebuffer function below is called when a new file is loaded:

?
bool TextDocument::init_linebuffer()
{
    ULONG i         = 0;
    ULONG linestart = 0;
    // allocate the line-buffer
    if((linebuffer  = new ULONG[length]) == 0)
        return false;
    numlines = 0;
    // loop through every byte in the file
    for(i = 0; i < length; )
    {
        if(buffer[i++] == '\r')
        {
            // carriage-return / line-feed combination
            if(buffer[i] == '\n')
                i++;
            // record where the line starts
            linebuffer[numlines++] = linestart;
            linestart = i;
        }
    }
    linebuf[numlines] = length;
    return true;
}

The init_linebuffer function does two things: allocate space for the line buffer, and then process the file. Because we don't know how many lines of text there will be until we process the entire file, how do we know how big to make the line-buffer? The short answer is, we can't possibly know this. A real TextDocument class would either dynamically reallocate it's line-buffer as it encountered more lines, or use some clever algorithms to limit the amount of memory consumed by the line-buffer.

For now we will cheat and make the linebuffer the same size as the file itself - this way we know we won't run out of space. Processing the file requires us to loop through each and every byte in the file, looking for a carriage-return character.

The algorithm itself is straight-forward. Whenever a carriage-return is encountered (followed by an optional line-feed), a new entry is added to the end of the linebuffer, which records the current start-of-line. The linestart variable is then made to "point" to the character after the carriage-return - the start of the next line. This continues until there are no more characters left in the file. The number of lines processed is kept updated in the numlines variable.

Retrieving lines of text

Now that our TextDocument implements a rudimentary line buffer, line-based lookups will be very fast. The following TextDocument::getline function shows how we will access each line of text from the TextDocument:

?
ULONG TextDocument::getline(ULONG lineno, char *buf, size_t len)
{
    char *lineptr;
    ULONG linelen;
    // find the start of the specified line
    lineptr = buffer + linebuffer[lineno];
    // work out how long it is, by looking at the next line's starting point
    linelen = linebuffer[lineno+1] - linebuffer[lineno];
    // make sure we don't overflow caller's buffer
    linelen = min(len, linelen);
    memcpy(buf, lineptr, linelen);
    return linelen;
}

The function works by using the lineno parameter as a direct index into the linebuffer array. The offset stored in linebuffer[lineno] is added to the real buffer of text, resulting in a pointer to the start-of-line. The length of the line (in characters) is calculated by subtracting the next line's offset from the current line's. Once the correct offset and length have been calculated, the raw line content is copied into the caller-supplied buffer. It's simple but it works.

?
char buf[200];
m_pTextDocument->getline(5, buf, sizeof(buf));

The example above shows how to retrieve a buffer of text at line number 5.

Drawing the lines of text

The whole point of accessing the TextDocument in a line-by-line manner is to make our lives simpler when it comes to drawing the text in our TextView control.

The entire drawing and painting logic in our TextView will be centered around the WM_PAINT message. In fact, our WM_PAINT handler will be the only place in the entire program where any form of painting takes place. As with every win32 program, the painting framework starts life as a basic BeginPaint / EndPaint sequence:

?
LONG TextView::OnPaint()
{
    PAINTSTRUCT ps;
    BeginPaint(m_hWnd, &ps);
    // do painting
    EndPaint(m_hWnd, &ps);
    return 0;
}

The function above simply validates the device-context and returns - and because our window-class for the TextView specified that there is no background brush, not even a single pixel is displayed.

At this point, all we have is a basic drawing framework, with the update-area specified in the PAINTSTRUCT::rcPaint RECT structure. The diagram below illustrates this update rectangle in red.

Our task is to take this single, arbitrary rectangular region and convert it into a series of horizontal spans that will be filled with lines of text. In the diagram above the update region has been split into three such spans. The variables first and last denote the first and last lines that require updating.

?
LONG TextView::OnPaint()
{
    PAINTSTRUCT ps;
    BeginPaint(m_hWnd, &ps);
    ULONG first, last;
    ULONG i;
    // figure out which lines to draw
    first = ps.rcPaint.top    / m_nFontHeight;
    last  = ps.rcPaint.bottom / m_nFontHeight;
    // draw the display line-by-line
    for(i = first; i <= last; i++)
    {
        PaintLine(ps.hdc, i);
    }
    EndPaint(m_hWnd, &ps);
    return 0;
}

The OnPaint code above is simple but effective. The first two lines we added were "first = " and "last = ". These are used to work out the starting and ending row which encompass the update region. We divide by the current font-height because we want to convert from pixels to logical lines.

Note that we have not taken into account the horizontal extents of the update region. This is deliberate, as it is simpler at this stage to draw each line in it's entirety, and let the device-context's update-region clip our output if we draw too much.

The individual line drawing has been deferred to separate function:

?
LONG TextView::PaintLine(HDC hdc, ULONG nLineNo)
{
    TCHAR buf[LONGEST_LINE];
    ULONG len;
    RECT  rect;
    GetClientRect(m_hWnd, &rect);
    // work out where the line should be drawn
    rect.top    = nLineNo  * m_nFontHeight;
    rect.bottom = rect.top + m_nFontHeight;
    // get the data for this single line of text
    len = m_pTextDocument->getline(nLineNo, buf, LONGEST_LINE);
    // draw text and erase the entire line background at the same time
    TabbedExtTextOut(hdc, &rect, buf, len);
    return 0;
}

As you can see drawing a line of text is relatively simple. The first thing we do is work out the pixel coordinates of where to draw the line. We use the window's client area as our starting point, and then adjust the top and bottom to describe the line as a simple rectangular region in pixel-based coordinates.

Once we have the line's bounding rectangle we use a further helper function to draw the text - TabbedExtTextOut. I won't include this function here - just look in the sources - suffice to say, TabbedExtTextOut is a simple wrapper function around the TabbedTextOut API, withthe added feature that it also erases any background area that is not taken up by actual text - similar to the ExtTextOut API.

The actual line of text we want to draw is retrieved from the TextDocument object using the getline method we developed earlier. It doesn't matter if we change how we store our text-file inside the TextDocument, as long as we preserve the "getline" interface the TextView and TextDocument can be entirely separate entities.

Note that our simple text-output at this stage is exactly that - we don't handle control characters, syntax colouring or scrolling. One step at a time though..

Coming up in Part 3

At this point we have a very simple text-viewing capability, but it is quite limited in what it can do. There is no scrolling, no keyboard or mouse input, no selection or highlighting, no colouring and no editing. Don't let this discourage you though - it is very important that we have a simple design with which to start off with.

If you are reading this tutorial series then you probably want to see how a real text-storage component is implemented - i.e. how large files are loaded, data structures managed etc. This will be covered in a future tutorial quite soon, but for now I want to get the basic graphical interface working first.

The next tutorial will therefore look at adding scrolling support, so at least we will be able to view an entire text document. Once we have finished that, we will look at mouse input, as this will be one of the most difficult areas to implement. It will require careful coding in both the mouse and drawing routines as we have to handle cursor placement and selection highlighting at the same time.

‹ Neatpad Overview up Scrollbars & Scrolling ›

18 Feb 2005

Posted in Tutorials

win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Scrollbars & Scrolling | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
neatpad3.zip

Published on 22 Jan 2012

46.63 Kb

Download

Scrollbars & Scrolling

Design & Implementation of a Win32 Text Editor

Welcome to the third installment of the "Design and Implementation of a Win32 Text Editor" article series! In this part we will look at adding scrollbars and scrolling to our TextView control.

Scrolling in Win32

In Windows, scrolling is divided into three areas: The first is the physical scrollbars, whether they be scrollbars built into a window, or separate scrollbar controls. There is a complete scrollbar API to set and retrieve the positional information described by a scrollbar - GetScrollPos, SetScrollInfo etc.

The next area is the window-messages that scrollbars send to their parent window when they are manipulated by the user - i.e. the WM_VSCROLL and WM_HSCROLL messages. These special scrolling messages can be used by an application to update it's user-interface in response to the user's interactions.

The last category is the GDI scrolling API - i.e. ScrollWindow and ScrollWindowEx. Although they are grouped in the same category as the "regular" scrollbar API in the Platform SDK, these two GDI routines don't have anything to do with scrollbars - instead they are used to move/offset a bitmap region within a window to give the illusion of scrolling.

We will have to bring all these areas together to implement full scrolling support in our TextView control.

Scrollbars - built in or separate?

The first thing I want to discuss before we start is what type of scrollbar to use. We have two choices - "built-in" scrollbars which would be part of the TextView's non-client window area, or separate scrollbar controls.

There isn't much difference between the two types. With built-in scrollbars, the WM_xSCROLL messages are sent to the same window to which the scrollbars belong. With separate scrollbar controls, the scrollbar messages get sent to the control's parent window. However it would be very simple to forward the scrollbar messages from a parent window to the real TextView window, so this wouldn't be any kind of set-back.

The real difference comes when we have to position our TextView control, because the scrollbar controls have to be carefully positioned at the same time to make sure they are aligned correctly at the bottom/right edges of the TextView. We would probably delegate this task to a "container" window which managed the layout of a single TextView and it's associated scrollbars.

Whilst separate scrollbar controls can provide additional flexibility (we can add grippers, buttons etc alongside the scrollbars), the extra work involved would detract from the task at hand. So for the time being the TextView will use normal built-in scrollbar controls - however later on in the tutorial series we will look at how to utilize separate controls because it is actually very simple, but just a little tedious to be concerned with at the moment.

Scrollbar settings

A scrollbar's state (i.e. it's range and current thumb position) is set using the SetScrollInfo API, using a SCROLLINFO structure to specify each attribute. This structure is shown below:

?
struct SCROLLINFO
{
    UINT cbSize;
    UINT fMask;
    int nMin;
    int nMax;
    int nPage;
    int nPos;
};

The diagram below illustrates these four scrollbar properties and how they relate to each other. For the sake of this small example, the data range that the scrollbar represents is 100 lines. Although I've used a picture of a horizontal scrollbar, a vertical scrollbar is no different in operation.

The nMin and nMax values represents the total number of "scrolling units" (or lines) that the scrollbar contain. For our TextView, we would set nMin to be zero as it makes no sense to allow scrolling to negative line numbers. nMax must be the total number of lines in the text document, minus one - in the case of our "100 line file", nMax must be 99.

The nPos value represents the current scrollbar position, in "scrolling units". This is not a pixel or coordinate based value, rather it is an arbitrary value somewhere within the range nMin...nMax.

This leaves us with the "odd one out" - nPage. This value has nothing to do with the scrolling range, the number of lines in a file or the current scrollbar position. It is used purely to specify how many scrolling units there are in the current window client-area. For example, if our window was big enough to hold 15 lines of text, then we would set nPage to equal 15. The built-in scrollbar will use this value to work out how big to make the scrollbar thumb - at no time do you have to do anything complicated to work out how big the thumb should be.

Note that the maximum thumb position will never equal nMax because the thumb width prevents it from reaching this far. Therefore we have two "maximum" values that relate to our scrollbar - what the scrollbar requires for it's si.nMax value, and what results as the thumb position maximum value - which is always nPage units less than the scrollbar's nMax.

Adding scrollbars to a Window

Adding scrollbars to a window is very simple - all we have to do is add the WS_HSCROLL and WS_VSCROLL window styles, which we can do when we create the window:

?
CreateWindowEx(WS_EX_CLIENTEDGE, TEXTVIEW_CLASS, 0, WS_VSCROLL | WS_HSCROLL ...

Before we can do anything useful with our scrollbars we must create some variables which will represent the "scrollbar state" for TextView C++ class:

?
class TextView
{
...
    ULONG m_nVScrollPos;
    ULONG m_nHScrollPos;
    ULONG m_nVScrollMax;
    ULONG m_nHScrollMax;
    int m_nWindowLines;
    int m_nWindowColumns;
    ULONG m_nLongestLine;
    ULONG m_nLineCount;
};

These variables (when initialized) will allow us to take into account the current scrollbar position when we are drawing our lines of text into the display.

  • m_nVScrollPos and m_nHScrollPos
    Represents the vertical and horizontal scrollbar thumb positions.
  • m_nVScrollMax and m_nHScrollMax
    Represents the maximum values that the scrollbar thumb positions can reach (not the maximum number of columns/lines in the file).
  • m_nWindowColumns and m_nWindowLines
    Stores the width and height (respectively) of the client area.
  • m_nLongestLine and m_nLineCount
    Hold the real text-file "dimensions".

Note that the m_nLongestLine variable has been introduced. This is used to represent the maximum horizontal scrolling extent. It should be obvious why we need to use the longest line in the text document to determine this horizontal scrolling range, and not some arbitrary fixed value.

?
ULONG TextDocument::longestline(int tabwidth);

The TextDocument has been given the task of calculating the width (in logical text units) of the longest line. The current tab-width setting must be specified so that tab characters can be taken into account. I won't include the code necessary to perform this task - just take a look at the sourcecode download.

Configuring the scrollbars

Repeated Win32 projects have taught me that scrollbars only really need to be setup in one place in a program - during a window-size change. A single function can be used to setup both the horizontal and vertical scrollbars at the same time:

?
VOID TextView::SetupScrollbars()

First of all a SCROLLINFO structure is configured to set the vertical scrollbar properties:

?
VOID TextView::SetupScrollbars()
{
    SCROLLINFO si = { sizeof(si) };
    si.fMask = SIF_POS | SIF_PAGE | SIF_RANGE | SIF_DISABLENOSCROLL;
    si.nPos  = m_nVScrollPos;         // scrollbar thumb position
    si.nPage = m_nWindowLines;        // number of lines in a page (i.e. rows of text in window)
    si.nMin  = 0;
    si.nMax  = m_nLineCount - 1;      // total number of lines in file (i.e. total scroll range)
    SetScrollInfo(m_hWnd, SB_VERT, &&si, TRUE);
...

The horizontal scrollbar is configured in a very similar way:

?
...
    si.nPos  = m_nHScrollPos;         // scrollbar thumb position
    si.nPage = m_nWindowColumns;      // number of columns in the window
    si.nMin  = 0;
    si.nMax  = m_nLongestLine - 1;    // width of longest line (i.e. total scroll range)
    SetScrollInfo(m_hWnd, SB_HORZ, &si, TRUE);
...

The very last thing to do is calculate the maximum positions that the scrollbar thumbs can take:

?
...
    m_nVScrollMax = m_nLineCount   - m_nWindowLines;
    m_nHScrollMax = m_nLongestLine - m_nWindowColumns;
}

It is important that you understand these last two lines. The m_nVScrollMax and m_nHScrollMax values are not used to set the scrollbar's si.nMax properties - the m_nLongestLine and m_nLineCount are used for this purpose. Instead, the m_nxScrollMax values represent the maximum thumb positions - much more useful for our purposes because we will use these values alot in the TextView.

Window size affects scrolling range

The first thing we must understand is that whenever we resize our TextView control, the amount of visible text within that window will change, and therefore the scrollbar's nPage value must change to reflect this. Therefore when our TextView window resizes we can react to the WM_SIZE message that is received:

?
case WM_SIZE:
    return tvp->OnSize(LOWORD(lParam), HIWORD(lParam));
?
LONG TextView::OnSize(int width, int height)
{
    m_nWindowLines   = min(height / m_nFontHeight, m_nLineCount);
    m_nWindowColumns = min(width  / m_nFontWidth,  m_nLongestLine);
    if(PinToBottomCorner())
        RefreshWindow();
    SetupScrollbars();
}

The first two lines simply work out how many lines/columns of text there are in the window. The min() function is used to handle the case when the window can display more text than there is available (i.e. when the entire file fits within the window).

Once these two values have been calculated the scrollbars can be configured using the SetupScrollbars function we wrote above. This leads us onto the next question:

What is "Pinning" ?

Imagine the following scenario. We have loaded a text document and scrolled right the way down to the bottom. We then drag the bottom window-border down to make the window larger. The question is, what happens to the file contents when we do this? We have two options to choose from, both of which are acceptable:

Do we leave the current scrollbar position intact and expose "void" space at the end of the file? This option would not affect the file's position - i.e. it would remain static. Some people prefer this because the text display remains in the same place on the screen.

The other option is to drag the file contents down at the same time (exposing more content at the top of the window), adjusting the scrollbar position at the same time, so that we always have a window full of text - in effect, "pinning" the file-content to the bottom edge of the window's client-area.

Have a look at the regular notepad utility and see what it does - you will find that it (or rather, the standard multi-line edit control) "pins" it's content to the bottom-right corner of the control when the control is resized. This is the behaviour that we will use in our edit control.

?
bool TextView::PinToBottomCorner()
{
    bool repos = false;
    if(m_nHScrollPos + m_nWindowColumns > m_nLongestLine)
    {
        m_nHScrollPos = m_nLongestLine - m_nWindowColumns;
        repos = true;
    }
    if(m_nVScrollPos + m_nWindowLines > m_nLineCount)
    {
        m_nVScrollPos = m_nLineCount - m_nWindowLines;
        repos = true;
    }
    return repos;
}

The function above simply adjusts the scrollbar position to make sure that it is always within the scroll-range, and returns a boolean value to indicate if anything changed - so we know if we need to redraw the window or not.

Note that the alternative (exposing empty space at the end rather than dragging the file down) would also be acceptable - in fact this is what Visual Studio and many other editors do. We might look at adding this behaviour as an optional extra later on in the series. Personally I prefer the "pinning" method much more so that's what I have implemented first of all.

Taking the scrollbar position into account when drawing

Currently our text-display is quite primitive because no scrollbar information is used when painting the display. Subsequently the text-file is firmly rooted to the top-left of the TextView client-area and we have to drag the window larger if we want to see more text. Fortunately very little work is required to make our TextView fully scrollable.

If you remember from the last tutorial, the TextView::OnPaint routine calculated the first and last rows of text to update using a simple formula:

?
first = ps.rcPaint.top    / m_nFontHeight;
last  = ps.rcPaint.bottom / m_nFontHeight;

It is a simple matter to take the vertical-scrollbar position into account:

?
first = m_nVScrollPos + (ps.rcPaint.top    / m_nFontHeight);
last  = m_nVScrollPos + (ps.rcPaint.bottom / m_nFontHeight);

Basically what this does is change the line-index that we draw, so as the scrollbar moves down, the lines of text effectively move up the display. We still draw lines at the same physical location within the window, but we draw different lines to give the illusion that we are scrolling through the document.

Now that we can correctly identify which logical line of text needs updating, we can look at the actual text-output.

?
void TextView::PaintLine(HDC hdc, ULONG nLineNo)
{
    RECT rect;
    GetClientRect(m_hWnd, &rect);
    // calculate rectangle for entire length of line in window
    rect.left      = -m_nHScrollPos * m_nFontWidth;
    rect.right     = rect.right;
    rect.top       = (nLineNo - m_nVScrollPos) * m_nFontHeight)
    rect.bottom    = rect.top + m_nFontHeight;
    // rest of function body omitted
    // draw text and fill line background at the same time
    TabbedExtTextOut(hdc, &rect, buf, len);
}

First of all understand the horizontal text-positioning - the rect.left and rect.right values: When the horizontal scrollbar's position is increased (i.e. we scroll to the right), we would expect the page to scroll to the left. So as the scroll position increases, the text position must decrease. This is why the "-m_nHScrollPos " is used. This logical text position is then multiplied by the current font-width to produce a pixel-based coordinate that can be used for drawing. This is ideal for fixed-width font displays.

The right-most edge of the line of text must still be fixed to the right-edge of the window, so this value is left unchanged. This basically results in the following occurance: As we scroll to the right, the length of text that we draw gets larger and larger because the starting x-coordinate becomes more and more negative. If the device-context didn't clip our output, lines of text would look something like this:

Of course, instead of offsetting our drawing to the left, we could have simply used the horizontal scrollbar position to find the correct place to draw within the line of buffered characters, and (sensibly) do all drawing from a fixed x-coordinate of zero. This would work well for our simple text display. However "tabbed" text display poses a problem, and once we start more complex syntax colouring and variable-width fonts, this quickly becomes a poor choice. I am undecided at present what the best method is to handle the horizontal scrolling so we will use this simple method for now.

Now onto the vertical scrollbar position. Because our PaintLine function is being supplied with a "logical" line number (nLineNo) - which is relative to the start of the document - we must subtract the vertical scrollbar's position from this value to arrive at a zero-based index, relative to the top of the client-area. This value is then multiplied by the font's height to provide a pixel-based y-coordinate.

Scrollbar Messages

We are now at a point where we are ready to add real scrolling to the TextView. We will concentrate just on the WM_VSCROLL message, as the WM_HSCROLL handler is virtually identical. If you look at the Platform SDK documentation for WM_VSCROLL, it states that low-order WORD of wParam contains the scrolling code. This can be one of the following values: SB_TOP, SB_BOTTOM, SB_LINEUP, SB_LINEDOWN, SB_PAGEUP, SB_PAGEDOWN etc. Basically, it tells us which part of the scrollbar has been clicked by the mouse.

We'll start by looking at the SB_TOP and SB_BOTTOM messages. The handler for the vertical scrollbar will look like this:

?
LONG TextView::OnVScroll(UINT nSBCode, UINT nPos)
{
    switch(nSBCode)
    {
    case SB_TOP:m_nVScrollPos = 0;
        RefreshWindow();
        break;
    case SB_BOTTOM:m_nVScrollPos = m_nVScrollMax;
        RefreshWindow();
        break;
    ...
    }
    // update the scrollbar metrics
    SetupScrollbars();
    return 0;
}

The SB_TOP and SB_BOTTOM cases are incredibly simple. All that is required is for us to move the scroll position to either extreme of the scrolling range, and then redraw the entire window to reflect the change.

Reacting to the scrollbar "thumb" messages is also very simple:

?
case SB_THUMBPOS: case SB_THUMBTRACK:
    m_nVScrollPos = GetTrackPos32(m_hWnd, SB_VERT);
    RefreshWindow();
    break;
...

The GetTrackPos32 is just a simple wrapper function around GetScrollInfo:

?
LONG GetTrackPos32(HWND hwnd, int nBar)
{
    SCROLLINFO si = { sizeof(si), SIF_TRACKPOS };
    GetScrollInfo(hwnd, nBar, &si);
    return si.nTrackPos;
}

This is required because we want the full 32bit scrollbar value rather than the 16bit position that we get from the WM_VSCROLL message.

The remaining four cases (SB_LINEUP, SB_LINEDOWN, SB_PAGEUP and SB_PAGEDOWN) are slightly different:

?
case SB_LINEUP:
    Scroll(0, -1);
    break;
case SB_LINEDOWN:
    Scroll(0, 1);
    break;    case SB_PAGEUP:
    Scroll(0, -m_nWindowLines);
    break;    case SB_PAGEDOWN:
    Scroll(0, m_nWindowLines);
    break;
...

As you can see we have deferred the real work to a private scrolling function, which we will implement in just a moment:

?
VOID TextView::Scroll(int dx, int dy);

This function takes two signed integer parameters. Their purpose is to specify the direction and amount (in text units) in which to scroll the viewport. If you look at the message-handlers for SB_LINEUP/DOWN, you can see that to scroll up, we specify a value of -1, and to scroll down we specify a value of 1. Similarly, for SB_PAGEUP/DOWN, we scroll up/down by an entire page's worth of text (i.e. the number of lines currently in the window). We will let the Scroll function take care of making sure that we never scroll off the start/end of the document.

Scrolling the Viewport

Scrolling has been isolated inside a single function call. This has a number of advantages - understanding these will help you develop better programs in the future. The first (and most obvious) advantage is the modular design benefits it brings. We will be able to re-use the scrolling function in many other areas of the control (we have already used it twice for the vertical+horizontal scrollbar messages), but we will also make good use of this function when it comes to keyboard and mouse scrolling.

The biggest advantage is not at first obvious, but greatly simplifies our design. By designing a scrolling function that can pan the display around in two directions at once (horizontal and vertical) we can greatly enhance the mouse-scrolling interactions. Many controls resort to sending two scrolling messages simultaneously - one to scroll up/down, and one to scroll left/right. This has the potential to introduce scrolling glitches and artifacts, and is also a little clumsy in operation.

?
VOID TextView::Scroll(int dx, int dy)
{
    // make sure dx,dy don't scroll past the end of the document!
    // adjust the scrollbar thumb position
    m_nVScrollPos += dy;
    m_nHScrollPos += dx;
    if(dx != 0 || dy != 0)
    {
        // perform the scroll
        ScrollWindowEx(
            m_hWnd,
            -dx * m_nFontWidth,
            -dy * m_nFontHeight,
            NULL,
            NULL,
            0, 0, SW_INVALIDATE
           );
        SetupScrollbars();
    }
}

The function above is not quite complete - it is missing some important logic to ensure that dx and dy never allow us to scroll outside the boundaries of the current file. The source-code download does include this code however.

All that I really wanted to show was the the ScrollWindowEx API to update the display. The TextView::Scroll function is very simple at the moment but it does work well - i.e. it scrolls the window smoothly with no flickering. We will revisit this function in the next tutorial when we look at selection-scrolling with the mouse.

MouseWheel support

Whilst we are looking at scrolling we may as well implement support for mouse-wheel scrolling because it is incredibly simple to do. The WM_MOUSEWHEEL message was added with Windows 98 and Windows NT4 to implement mouse-wheel scrolling. In order to support this message we will need a new-ish Platform SDK installed, and we must also define the _WIN32_WINNT variable before #including <windows.h>, so that the message is available for us to use.

Our handler for WM_MOUSEWHEEL simply extracts the wheel-rotation delta (i.e. the forwards/backwards vector) and calls the real message-handler in our TextView class:

?
case WM_MOUSEWHEEL:
    return ptv->OnMouseWheel((short)HIWORD(wParam));

The handler uses the SPI_GETWHEELSCROLLLINES system-setting to work out how many lines to scroll:

?
LONG TextView::OnMouseWheel(int nDelta)
{
    int nScrollLines;
    SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &nScrollLines, 0);
    if(uScrollLines <= 1)
        uScrollLines = 3;
    Scroll(0, (-nDelta/120) * uScrollLines);
}

That's all it takes, because we have made good use of the Scroll function we developed to do the hard work. See, I told you it would be useful ;-)

Coming up in Part 4

I hope you will take the time to download the zipfile at the top of this tutorial and have a play with the latest incarnation of Neatpad. For such a small amount of code it is quite a useful tool already - it is a pretty effective text-document viewing application. Of course large files aren't handled at all but it should give you a good idea about where to proceed.

Mouse support will be the aim of the next tutorial. We will look at how to add focus and text-caret support, caret positioning with the mouse, and full text selection. This will require modifications to the drawing code of course. We will also implement "mouse scrolling" - i.e. where the text selection is extended beyond the window and the contents must be scrolled into view. The scrolling work we have done so far will make this task very simple.

‹ Loading a text file up Enhanced Drawing & Painting ›

20 Feb 2005

Posted in Tutorials

win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Enhanced Drawing & Painting | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
neatpad4.zip

Published on 22 Jan 2012

53.47 Kb

Download

Enhanced Drawing & Painting

Design & Implementation of a Win32 Text Editor

Welcome to the fourth installment of the "Design and Implementation of a Win32 Text Editor" article series! I realise that you were probably expecting a different tutorial this time around. However, it was only after starting to implement mouse selection and highlighting that I realised there wasn't proper support in the drawing "engine". I've decided to implement all of my text-output requirements in this one tutorial. So we will be covering tabbed-output, multi-coloured text (i.e. for syntax highlighting and selection highlighting) and the problem of ASCII control characters.

Example of coloured text with ASCII control characters.

Multi-coloured text

The first reason the drawing code needs revising is to support multi-coloured text. And I don't just mean syntax colouring (which will be covered in a later tutorial). Selection highlighting is the main reason text must be drawn using different colours, simply to distinguish between selected and unselected text. The image below illustates a line of text with the middle portion selected using the default system colours.

Note that the text isn't really selected - it is merely drawn using two different colour schemes. Just have a play with selecting this paragraph of text in whatever browser you are using, and understand that the "selection" you are making is really just a segment of text drawn in a different colour. So all we need to do for our TextView is divide up our text into the appropriate segments and call SetTextColor & SetBkColor to set the colours before calling ExtTextOut - there really isn't any magic involved here.

A common mistake when first starting a control such as this is to try drawing the selected text in the WM_MOUSEMOVE handler, using intricate fill modes, inverted rectangles or transparent text. This really is the worst approach you could take. The next tutorial will show how to update a selection using the mouse, but all the drawing will be happening in our WM_PAINT handler.

The drawing logic must therefore be able to handle combinations of any colour - because we must think ahead about syntax and block highlighting. And because a text file does not store colour or style information, character colours must be computed separately and applied to the displayed text at runtime.

The attributes of a single character will be represented by the ATTR structure, shown below:

?
typedef struct{
    COLORREF fg;      // foreground colour
    COLORREF bg;      // background colour
    ULONG style;   // font and style information
} ATTR;

Whenever a line of text is retrieved from the TextDocument, this text must be "colourised" before displaying it. Font, style and colour attributes will be computed by a separate routine, ApplyTextAttributes.

?
void TextView::ApplyTextAttributes( ULONG nLineNo,
                                    ULONG nOffset,
                                    TCHAR *szText,
                                    int nTextLen,
                                    ATTR  *attr)
{
   // loop over each character in the text-buffer
   for(int i = 0; i < nTextLen; i++)
   {
        // apply highlight colours
        if(nOffset + i >= m_nSelectionStart &&& nOffset + i < m_nSelectionEnd)
        {
            attr[i].fg = GetColour(TXC_HIGHLIGHTTEXT);
            attr[i].bg = GetColour(TXC_HIGHLIGHT);
        }
        // normal text colours
        else
        {
           attr[i].fg = GetColour(TXC_FOREGROUND);
           attr[i].bg = GetColour(TXC_BACKGROUND);
        }
        attr[i].style = 0;
    }
}

This function will be called whenever some text is about to be drawn. Two parameters are passed in - nLineNo and nOffset , which describe where in the file the text occurs. ApplyTextAttribrutes will use these two parameters to determine how to "format" the text - currently as selected or unselected.

?
TCHAR buff[100];
ATTR    attr[100];
ULONG fileoff;
// get some text from the TextDocument
int len = m_pTextDoc->getline(nLineNo, 0, buff, 100, &fileoff);
// calculate colour and font information
ApplyTextAttributes(nLineNo, fileoff, buff, len, &attr);

The formatting information is returned through the ATTR array which must be the same size as the szText buffer which holds the text. For the moment this function is sufficient to describe "normal" and "highlighted" text, but will not perform any form of syntax highlighting. I imagine that the function prototype may have to change slightly because syntax highlighting will have additional requirements.

Multi-font display

In part one of this series I wrote that I wanted to restrict the TextView to a single, fixed-width font. The entire reason for using fixed-width fonts was to keep the TextView code as simple as possible - especially the mouse-cursor placement logic. However even a fixed-width text display is not truly fixed-width, because of TAB characters which cause variable sized gaps in the lines of text. Having to deal with TABs means writing additional code to parse each line of text - to determine where in the line the mouse has been placed. My reasoning for supporting variable-width fonts is this: if we have more work to do, we may as well do it properly and handle all types of font.

In order to manage multiple fonts, we must devise a strategy to manage these fonts somehow. The FONT structure (defined below) holds a handle to a font (a normal HFONT) and also contains important information about the font's dimensions in the TEXTMETRIC structure.

?
struct FONT
{
    HFONT hFont;
    TEXTMETRIC  tm;
    int nInternalLeading;
    int nDescent;
};

An array of these fonts is stored as a member of the TextView class:

?
FONT  m_FontAttr[MAX_FONTS];
int m_nNumFonts;

The first element in this array (element zero) is always the default display font. Additional fonts can be added to this array but will not be used unless some form of syntax-highlighting is developed. I only imagine variations of fonts would be used in the control (i.e. normal and bold fonts) rather than completely different styles, but the TextView will support whatever fonts you decide on.

There are some added complications which will arise from the use of multiple fonts, which are highlighted by the picture above. The problem can be divided into three cases:

  • Fonts can vary in width and height. The width is not so much trouble, however fonts of different heights must be aligned so that their bases are on the same horizontal line.
  • The height of a line of text is not based on a single font any more - it must be big enough to hold the tallest font currently in use.
  • Smaller fonts do not fill the vertical extents of the line when painted (as can be seen above). This will require us to fill this extra "dead space" ourselves.

To solve these problems we must first understand how a font's structure is described by GDI. The diagram below illustrates the dimensions of a font as described by the TEXTMETRIC structure:

The Baseline of a font is measured by the Ascent dimension. Therefore if we keep track of the largest Ascent out of all the fonts we are using, we can work out the amount to vertically offset shorter fonts based on their individual Ascent value. For example, suppose we are using two fonts - "Courier New" with an ascent of 21, and "Lucida" with an ascent of 18. When drawing text using the Lucida font, we would have to offset this text down by 3 pixels in order to align the baselines correctly.

The second problem is easily solvable. Previous incarnations of Neatpad used the m_nFontHeight variable to keep track of the height of each line. This variable has now disappeared, to be replaced by a more meaningful value - m_nLineHeight. The TextView code demonstrates how this value is calculated in the OnSetFont member function:

?
LONG TextView::OnSetFont(HFONT hFont)
{
    m_nLineHeight = 0;
    for(int = 0; i < m_nNumFonts; i++)
    {
        m_nLineHeight = max(m_nLineHeight, m_FontAttr[i].tm.tmHeight);
    }
}

The final painting problem is easily solved because there is a handy API at our disposal. ExtTextOut allows us to specify a rectangular region to paint when drawing some text:

?
ExtTextOut(HDC hdc, int x, int y, UINT flags, RECT *rect, TCHAR szText, ...);

The x and y parameters specify where the text is to be drawn. The rect parameter specifies the background rectangle. As long as this rectangular region fills the entire height of the line we can be sure to paint the background properly.

Something we must be careful with is how we tell ExtTextOut to fill the background. The ETO_OPAQUE flag can be used to achieve this effect. However, we must also specify ETO_CLIPPED as well, otherwise we will get some strange effects when using ClearType fonts. The picture above illustrates the problem - even the string length was calculated using GetTextExtentPoint32 (and the background rectangle set to this size), when ExtTextOut was called the string "bleeds" outwards by 1 pixel either side - an oddity unique to the way ClearType text is displayed. Specifying ETO_CLIPPED prevents this problem.

Line Spacing

There is something that I want to mention at this point: Many people are sometimes confused as to how the height of a font (or line of text) must be calculated. There are usually four different methods I see of obtaining a font's height, shown below:

?
TEXTMETRIC tm;
GetTextMetrics(hdc, &tm);
height1 = tm.tmHeight;
height2 = tm.tmHeight + tm.tmExternalLeading;
height3 = tm.tmHeight + tm.tmInternalLeading;
height4 = tm.tmHeight + tm.tmInternalLeading + tm.tmExternalLeading;

Method #1 is correct and is what many editors utilize, because the height of a font is exactly determined by the tmHeight value. However, method #2 is more correct for a text-editor. The External Leading of a font is the value (as assigned by the font's designer) that should be added in-between lines of text when displayed in a paragraph.

In other words, a multi-line display should use this value when displaying lines of text. The problem is, the external-leading is not taken into account when text is drawn using the standard Windows text routines (TextOut/DrawText). When a line of text is drawn using TextOut, the background is only filled to cover the tmHeight of the font and does not include the tmExternalLeading value. This results in gaps in-between lines of text, which makes people think that they are doing something wrong. The gaps between lines must be filled manually if the correct results are desired. Our multi-font display handles this extra line-spacing correctly by using ExtTextOut and enlarging the background rectangle appropriately.

As a final note, the third and fourth methods are wrong and should not be used to calculate the height of a font, or the height of a line of text. Study the diagram above to see how the various dimensions relate to each other.

As an extra feature I have also added a new message to the TextView - TXM_SETLINESPACING. This message is used to set extra spacing above and below each line of text, in addition to the font's external leading value.

?
TextView_SetLineSpacing(hwndTextView, 3, 2);

The example above instructs the text-view to add 3 pixels above each line, and 2 pixels below each line (resulting in each line of text being 5 pixels taller). I think people will find this feature extremely useful, as many fonts sometimes don't look very good unless extra line-spacing is included.

The picture above illustrates the various components of a line of text in Neatpad.

ASCII Control characters

The other reason I decided to revise the drawing code was to take into account control-characters (i.e. ASCII control characters 0 - 31). Many simple text editors (such as regular Notepad) do not handle control characters at all. The NUL character (ASCII value 'zero') is never displayed for example - and how could it be - there is no visual representation for this character. The other control characters are equally problematic because they are designed to have function rather than appearance - a backspace or linefeed character also cannot be displayed for this reason. The solution is to duplicate what the Scintilla edit component does to display control characters:

The line of text above has two control-characters embedded it - the NUL character (value zero) and the SYN character (value 24). I'd never seen this method of displaying control-characters until I looked at Scintilla, however it does seem like a neat way of dealing with the problem. Every character in the range 0 to 31 is displayed using inverted colours, with a rectangular border to enclose the text. These "graphic/textual" representations of the ASCII control characters stand out from the surrounding text and are a great visual aid in my opinion.

Enhancing the text-output has again introduced further complexity. The "new style" control-characters are not fixed-width - the borders introduce a 4-pixel overlap which means that the display can no longer be fixed-width only. Actually it was the introduction of these control-character "bitmaps" that prompted me to switch to a fully variable-width font display.

The images that make up the control-characters will be quite complicated to draw - more so than just doing a simple "TextOut". In an ideal world we could pre-calculate the bitmaps and store them in an off-screen buffer, and "BitBlt" them to the screen each time we needed to - this would be an efficient method to draw these characters.

Unfortunately nothing is that simple. We must be able to deal with control-characters in multiple colours (i.e. when they are selected/highlighted or part of a syntax string). We also need to take into account the different fonts, heights and styles that I want the TextView to handle. This means that our drawing code will have to be modified to deal with the occurance of ASCII control codes and handle them appropriately with a special routine DrawCtrlChar.

The basic breakdown of drawing operation is as follows (for black-on-white text):

  1. Fill the background white.
  2. Make a rectangle the width of the text and the same height as the letters.
  3. Draw this rectangle in black.
  4. Expand this rectangle 1 pixel at the top and bottom, and contract it by 1 pixel at the left and right. (i.e. make it taller and thinner by 1 pixel in each dimension).
  5. Draw a second black rectangle - this gives the illusion of a rounded rectangle.
  6. Draw the text in white.

The TextView will of course support the option not to display control-characters this way - I can imagine some scenarios where this would be useful. In this case control-characters can be replaced by a single character '.', or just drawn as best they can.

NeatTextOut

At this point we need to develop a new text-output routine which can handle drawing an aribitrary segment of text (possibly containing control-characters), in a specific font and colour. NeatTextOut (prototype shown below) is similar to TabbedTextOut, with the exception that colour and style information is supplied in the ATTR structure. This function also returns the width of the displayed text (in pixels) so that the calling function can keep track of where to draw (and also when to stop drawing when the text goes outside the window).

?
int NeatTextOut( HDC hdc,
                 int xpos,
                 int ypos,
                 TCHAR *szText,
                 int nLen,
                 int nTabOrigin,
                 ATTR  *attr
               );

I really don't see the value of including the source-code here for you to see, because it is obviously included in the TextView class and is fully documented for you to see there. Suffice to say, if you don't want to support control-characters you can quite easily replace this function with a normal TabbedTextOut.

Improved drawing engine

The whole thrust behind this tutorial is to be able to render text to the TextView window, using the ATTR structure as a guide for how the text should look. The drawing logic is shared between the following functions:

The first function - OnPaint - hasn't changed at all, whereas the PaintLine function has actually been reduced in functionality. All it does is work out where to draw the line, and then passes control to a further function, PaintText, to do the actual work. My reasoning behind this decision is to allow PaintLine to handle selection margins and line-numbers at some time in the future, and to allow PaintText to just draw the text and nothing else.

The PaintText function is therefore responsible for drawing an entire line of text. It retrieves text from the TextDocument and applies colour formatting to the text by calling ApplyTextAttributes before calling NeatTextOut to display the text.

The benefit of this design is that the drawing code is completely independent of the rest of the TextView. I like this approach because it means that text drawing is isolated into one single place. Any future syntax-highlighting, mouse selection, bookmarks or any other changes will not impact on the drawing code because it is completely dumb - all it knows how to do is draw text in particular styles. It will be up to the syntax highlighter to create these styles, but this is a separate, well-contained problem of it's own.

Understand that when we draw text we have two parallel arrays - one holding the text and the other holding each character's colour attributes. Iterating through these arrays and drawing the text character by character will be very slow, so instead PaintText collects together as much text as possible before drawing it. What it does is identify consecutive characters that share the same colour and font, and outputs each "span" of text in one go. The result is very fast and in practise is not really any slower than doing a single TextOut. The important part of the PaintText routine is shown below.

?
int i     = 0;         // current character position
int lasti = 0;         // last character position
int xtab  = xpos;
char buff[100];
ATTR  attr[100];
//
// Display the text by breaking it into spans of colour/style
//
for(i = 0; i <= len; i++)
{
    // if the colour or font changes, then need to output
    if(i == len ||
       attr[i].fg    != attr[lasti].fg    ||
       attr[i].bg    != attr[lasti].bg    ||
       attr[i].style != attr[lasti].style)
    {
        xpos += NeatTextOut(hdc, xpos, ypos, buff + lasti, i - lasti, xtab, &attr[lasti]);
        lasti = i;
    }
}

I'm not going to include any more code in this tutorial because the sourcecode download is well documented - as always ;-) and it is very easy to see what is going on.

Coming up in Part 5

Mouse input and selection definitely be the subject of the next tutorial. It was necessary to delay the this subject because without proper drawing support selection highlighting would not have been realistically possible. We will also implement "mouse scrolling" - i.e. where the text selection is extended beyond the window and the contents must be scrolled into view. The scrolling work we have done so far will make this task very simple.

‹ Scrollbars & Scrolling up Mouse Selection & Highlighting ›

19 Apr 2005

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Mouse Selection & Highlighting | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
neatpad5.zip

Published on 22 Jan 2012

72.41 Kb

Download

Mouse Selection & Highlighting

Design & Implementation of a Win32 Text Editor

Mouse input has proven to be the most intricate and difficult to write part of Neatpad to date. It hasn't been helped by the fact that Neatpad now supports variable-width fonts, so in some ways I am still unsure if this extra complexity is a good thing or not (from a tutorial / learning point of view). However, if I had stuck with fixed-width fonts it would be quite a task for anyone to move from that limited capability to a fully variable-width display, so from that perspective I think I made the right decision...

Most of the complexity has been caused by the deliberate separation between the TextView and TextDocument classes. If the TextView had direct memory-access to the underlying file some of the code could be a little simpler. However if we are to move to a 4Gb file-editor we must hide the memory-management behind a TextDocument class and force a strict interface between GUI and file-management, accessing the file content in small chunks at a time. It makes our code cleaner, but a little more difficult. Anyway, without further ado let's look at what happen's when a person clicks the mouse-pointer inside our Neatpad window.

Carets, Focus and Activation

In Windows, whenever the mouse is clicked inside a control, the expected behaviour is for the window to receive the input-focus and display some form of graphical feedback which indicates this change in focus. This is a consistent user-interface detail present throughout all of Windows.

The default behaviour in Windows is to send a WM_MOUSEACTIVATE message during this intial window activation. However, at no point does the target window actually receive the input focus due to this mouse-click - this is a detail not known or understood by many people. We must process this WM_MOUSEACTIVATE message manually, and set the focus ourselves:

?
case WM_MOUSEACTIVATE:
    return ptv->OnMouseActivate();
?
LONG TextView::OnMouseActivate()
{
    SetFocus(m_hWnd);
    return MA_ACTIVATE;
}

So now whenever the mouse is clicked inside our TextView, it will receive the input focus. More importantly though, the TextView will now receive an additional message - WM_SETFOCUS. It is during the processing of this message that we can create and show a caret.

?
case WM_SETFOCUS:
    return ptv->OnSetFocus();
?
LONG TextView::OnSetFocus()
{
    DWORD nWidth = 2;
    SystemParametersInfo(SPI_GETCARETWIDTH, 0, &&nWidth, 0);
    CreateCaret(m_hWnd, (HBITMAP)NULL, nWidth, m_nLineHeight);
    ShowCaret(m_hWnd);
    RefreshWindow();
}

The text-caret will be just the same as in Notepad or Visual Studio. We specify a NULL bitmap handle to create a solid blinking caret. It will be exactly 2 pixels wide unless we are on Windows 2000 and above, in which case the SystemParametersInfo request for SPI_GETCARETWIDTH will succeed and we will use that setting instead. The caret will also be the same height as a line of text. This is a pretty standard way to show show a text caret in Windows.

Whenever the TextView loses the input-focus, it will receive the WM_KILLFOCUS message and we can hide the text-caret and delete it:

?
case WM_KILLFOCUS:
    return ptv->OnKillFocus();
?
LONG TextView::OnKillFocus()
{
    HideCaret(m_hWnd);
    DestroyCaret();
    RefreshWindow();
}

Note that each time the TextView receives or loses focus, the window entire is redrawn. This will enable us to draw the text-selection in different colours depending on whether the window has focus or not. We now move onto the problem of placing the caret in the correct position whenever the mouse is clicked inside the window. Before we go this far however, we have a decision to make.

TextView Coordinates - offset or coordinate based?

A decision must be made before we go any further. We must decide how to keep track of the current cursor position and selection start/end positions. We have two choices, described below.

The first option is to use a single 32bit file-offset to store all "coordinates". In other words, this is much like a HexEditor or how the existing EDIT control works. The advantage of this method is it is very simple to store, manage and "work with" file offsets - all data accesses are much simpler to perform because everything is offset/buffer based. The disadvantage is that a file-offset does not directly translate to the GUI - remember that we have to use a line buffer to access the underlying file. There is the potential here to introduce extra dependencies on the line-buffer management that may cause performance problems.

The second option is to use a "text-coordinate" system. This would require using two values - one for the line number, and one for the character-offset (or column number) within that line. The advantage of this technique is that it translates directly to what you see on the screen. Moving the caret around with the mouse and keyboard (i.e. user GUI actions) will be potentially a lot easier than the first method because we are using a more natural coordinate system for those actions. The disadvantages are two-fold. Firstly, data-access is now more difficult, and all data-accesses will be required to go through the line-buffer to find the true file-offset - again with the potential performance problems. The second disadvantage is the text-painting. It is far more cumbersome to perform comparisons with an "x,y" coordinate than it is a single value. For example, the decision as to whether to highlight a character based on an x,y coordinate is much more complicated than performing a simple integer comparison.

?
ULONG m_nCursorOffset;
ULONG m_nSelectionStart;
ULONG m_nSelectionEnd;

I have experimented with method two in the past and to be honest it was just as complicated as the "pure" file-offset method - the complications and performance issues just get moved to a different place. For this reason the TextView will use the simpler "file-offset" method of storing text coordinates. This will hopefully isolate the difficult issues just inside the mouse-input handling and make the rest of our code easier to write.

Placing the Text Caret

When the user clicks the mouse in a "normal" edit control, the text-caret is placed at the beginning of the nearest character to where the mouse is. It seems a kind of obvious thing to do, but this simple operation is going to be the most complex task we have tackled to date.

The first useful message we receive when the user clicks the mouse is WM_LBUTTONDOWN. The handler for this message is shown below and is actually quite simple. I have structured it in such a way that it doesn't matter if we are using offset-based or text-based coordinates.

?
LONG TextView::OnLButtonDown(UINT nFlags, int mx, int my)
{
    ULONG nLineNo;
    ULONG nCharOff;
    ULONG nFileOff;
    int xpos;
    // map the mouse-coordinates to a real file-offset-coordinate
    MouseCoordToFilePos(mx, my, &nLineNo, &nCharOff, &nFileOff, &xpos);
    SetCaretPos(xpos, (nLineNo - m_nVScrollPos) * m_nLineHeight);
    // erase any existing selection
    InvalidateRange(m_nSelectionStart, m_nSelectionEnd);
    // reset cursor and selection offsets to the same location
    m_nCursorOffset   = nFileOff;
    m_nSelectionStart = nFileOff;
    m_nSelectionEnd   = nFileOff;
    // set capture for mouse-move selection
    m_fMouseDown = true;
    SetCapture(m_hWnd);
    return 0;
}

There are two basic tasks that must be performed. The first is to identify which text-character within the file has been clicked - we need to retrieve the zero-based file-offset of this selected character (so we can keep track of the current cursor position). The second task is to position the text-caret next to the character we selected in step#1.

These complex operations have been isolated inside a single TextView member-function, MouseCoordToFilePos. The purpose of this function is to return the line number, character offset within that line (i.e. the column number), physical file offset, and finally the x-coordinate of the character as it appears on screen.

?
BOOL MouseCoordToFilePos (int mx,            // mouse x-coordinate
                          int my,            // mouse y-coordinate
                          ULONG *pnLineNo,      // [out] line number
                          ULONG *pnCharOffset,  // [out] column number
                          ULONG *pnFileOffset,  // [out] file-offset
                          int *px);           // [out] adjusted x coordinate

I'm not sure if I really want to include the full code this function here because it is likely I will keep tweaking it to try and make it as clear/simple as possible. However I do want to describe the basic operation so people can understand exactly what is involved in this operation.

Find the line-number

The first thing to do is work out which line of text is under the mouse. This is actually very straight-forward because we are using fixed-height lines of text (i.e. every line of text is the same).

?
ULONG lineno = (my / m_nLineHeight) + m_nVScrollPos;

If we were writing a word-processor or a HTML viewer the operation above would be alot more complex, but for our simple text-editor it is sufficient to just divide the mouse Y-coodinate by the line-height. As you will see below, knowing what line we are currently looking at is very important because we must parse each specific line of text in order to calculate the cursor x-position.

The GetTextExtent problem

Once we know what line of text we are dealing with, we must work out which character within that line has been selected. Due to the possibility of tabs and control-characters occuring in the line of text (or if we are using variable-width fonts), we must parse the entire line of text (from the start) to work out which character falls under the mouse.

In Windows there are many APIs which tell you how big a string of text is (in pixels). However there are no APIs which perform the opposite conversion - i.e. how many characters fit within the specified space. Starting with Windows 2000 two new routines were introduced to address this problem - GetTextExtentPointI and GetTextExtentExPointI.

However we can't rely on just Windows 2000, and due to the GUI-file separation of the TextView/TextDocument (i.e. accessing the content in chunks), we must devise our own strategy to work out which character has been selected.

We start by accessing the line of text in fixed-sized blocks. As we proceed in this manner, the width of each block of text is calculated using a function NeatTextWidth (which is basically a wrapper around GetTextExtentPoint32, but also takes into account tabs and control-characters). The mouse x-coordinate is then checked against this block of text to see if it falls inside.

The picture above should hopefully illustrate this process fairly clearly. It's not at all accurate (and it isn't intended to be). All we want at this stage is rough guess as to where the mouse has been placed. The code snippet below is basically what is happening here:

?
int curx    = 0;
int charoff = 0;
for(;;)
{
    // grab some text
    if((len = m_pTextDoc->getline(nLineNo, charoff, buf, TEXTBUFSIZE, &fileoff)) == 0)
        break;
    // find it's width
    int width = NeatTextWidth(hdc, buf, len, -(curx % TABWIDTHPIXELS));
    // does the cursor fall within this segment?
    if(mx >= curx && mx < curx + width)
    {
        // narrow down the search
    }
    // move onto the next block
    curx    += width;
    charoff += len;
}

Once the correct block of text has been identified we must narrow down the search using what is essentially a "binary chop" algorithm.

Binary-Chop

We are now working at the single character level. For efficiency reasons I really don't want to call NeatTextWidth for each character in turn so the binary-chop (or binary-search) is perfect for this situation. The diagram below shows the algorithm in action.

We keep track of the search using two variables, low and high, which specify offsets into the character-buffer we are searching. These offsets start at the two extreme ends of the buffer and then move inwards, narrowing the search down each interation.

For each iteration, we take the mid-point between low and high. We then compare the mouse coordinate to see which side of this mid-point the cursor falls. If it is to the left then we center in on this segment, and likewise if it is to the right of this midline. We will eventually get to the point where we have closed in on a single character (low is exactly one less than high) with the mouse somewhere in this small range of pixels.

?
int low   = 0;
int high  = len;
int lowx  = 0;
int highx = width;
while(low < high - 1)
{
    int newlen   = (high - low) / 2;
    width = NeatTextWidth(hdc, buf + low, newlen, -lowx-curx);
    if(mx - curx < width + lowx)
    {
        high  = low + newlen;
        highx = lowx + width;
    }
    else
    {
        low   = low + newlen;
        lowx  = lowx + width;
    }
}

In computer science terms this method has an efficiency of O(log2n) - actually a binary search is a very efficient algorithm, and even for very long lines of text it should still be quite fast. For variable-width fonts there really isn't any other way to perform this type of thing. Obviously for a fixed-width font display, we could simply scan through the whole line in one go but I won't bother "over-optimizing" just yet because it will just clutter the code up.

Snap to middle of character

It is at this point that we know which character has been clicked/selected with the mouse, and we have the x-coordinates of this character's starting and ending positions, in the lowx and highx variables:

The final detail to be implemented is to determine which side of the character to place the text-cursor (caret). Sloppy text-editors simply "round-down" to the start of each character (i.e. they just position the cursor at the start of the character by choosing the lowx coordinate). However a more natural "feel" can be achieved by using the center of each character to decide which side to place the caret.

?
if(mousepos > highx - FontWidth/2)
    caret = highx;
else
    caret = lowx;

Notice that the "TAB" character shown above has the selection-line positioned on the right-hand-side rather than the middle. This is a deliberate detail because I want to emulate the way Visual Studio places the cursor when it is positioned over a TAB (or any control-character which is also wider than a single letter).

Selecting with the mouse

Now that we are able to position the text-caret under any character within the TextView, we are ready to move onto mouse-selection. Cast your memory back to what we do when we process WM_LBUTTONDOWN - the m_nCursorOffset, m_nSelectionStart and m_nSelectionEnd variables were all set to point to the same location.

To extend the selection as we drag the mouse we can handle the WM_MOUSEMOVE message. Again we retreive the file-offset under the mouse using MouseCoordToFilePos. Now however, we can modify just the m_nSelectionEnd variable to "point" to this new offset, leaving m_nSelectionStart where it is. This has the effect of extending the selection. To have this reflected on the screen we must obviously redraw the display, and this is where the tricky part comes in.

?
LONG TextView::OnMouseMove(UINT nFlags, int x, int y)
{
    if(m_fMouseDown)
    {
        ULONG nLineNo, nCharOff, nFileOff;
        int px;
        MouseCoordToFilePos(x, y, &nLineNo, &nCharOff, &nFileOff, &px);
        // update the area that has changed
        if(m_nSelectionEnd != nFileOff)
        {
            InvalidateRange(m_nSelectionEnd, nFileOff);
            SetCaretPos(px, (nLineNo - m_nVScrollPos) * m_nLineHeight);
            m_nSelectionEnd = nFileOff;
            RefreshWindow();
        }
    }
}

The WM_MOUSEMOVE handler (above) is quite similar to the WM_LBUTTONDOWN handler. We first translate the mouse x,y coordinates to a file-offset. Assuming that this offset is different to the current cursor offset, we can reposition the text-caret and redraw the area of text between the old selection-end point and the new cursor position.

Invalidate a range of text

The key to a good selection/highlighting strategy is to only redraw the bare minimum at a time - we must only paint where there are changes and never anywhere else - simply to avoid flicker rather than for performance. The InvalidateRange member-function does exactly this.

?
LONG TextView::InvalidateRange(ULONG nStart, ULONG nFinish);

The two parameters (nStart and nFinish) specifies the range as file-offsets. It is the job of InvalidateRange to convert these two parameters to screen coordinates and cause just the specified region to be redrawn. This is the exact "opposite" to MouseCoordToFilePos - we are moving from file offsets back to screen coordinates now. You can use whatever coordinate system you want (file-offsets or text-coordinates) - it really doesn't matter. It is the concept of limiting the redraw to the change in selection that is important here.

Note that the display isn't actually redrawn using this function - the specified area is instead invalidated (using a series of calls to InvalidateRect), and the task of doing the actual drawing is left up to the WM_PAINT handler which we have already implemented.

Even though our WM_PAINT handler redraws whole lines at a time, it is because we have invalidated a specific area the update region for the window will clip our output and prevent us drawing over areas that didn't change.

The picture above is meant to illustrate a "selection in progress". The selection has been made in four steps, starting with the lightest blue segment. The basic idea is to break the task up into lines, calling InvalidateRect for each span of text. What I am trying to show is that a selection-change could be a small segment on one line, or a change involving multiple lines at the same time.

The InvalidateRange function (however it is implemented) must be able to handle these different scenarios correctly. There is little point in including the function body here so it is time to finish this part of the tutorial.

Coming up in Part 6

Mouse selection is quite a tricky subject but I hope I have covered it adequately for people to appreciate what is required for such a task. Also remember that this is a simple text editor - imagine how much more difficult it would be to write a real word processor or web-browser which has to handle many different types of text and graphics.

Part 6 will take what we have implemented here and add "mouse scrolling" to the equation.

‹ Enhanced Drawing & Painting up Scrolling with the Mouse ›

29 May 2005

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Scrolling with the Mouse | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
neatpad6.zip

Published on 22 Jan 2012

81.73 Kb

Download

Scrolling with the Mouse

Design & Implementation of a Win32 Text Editor

Originally I had planned to keep all of the mouse-related concepts together in one tutorial. The problem is, does mouse-scrolling belong in the mouse tutorial (part#5) or the scrolling tutorial (part#3)? In the end I've decided to make it a separate topic by itself. Actually it has worked quite well because it gives a good sense of progression between basic mouse selection (with no scrolling) and a fully working selectable, scrollable control.

Scrolling with the mouse

The basic idea behind "mouse scrolling" is to cause the window to scroll when the mouse is dragged outside the window whilst making a selection. Almost any application that hosts a window with scrollbars will support mouse-scrolling in some form or fashion. Try it with whatever browser you are using right now - select some text and whilst holding the left-button down, drag the mouse outside the browser window. The contents is automatically scrolled into view.

The very first step we must take is to detect when the mouse leaves the window and initiate some kind of appropriate scrolling action. Fortunately we already have TextView::Scroll (written in part#3) which we will be able to use for this tutorial.

Now, take a quick look at Notepad and you will see how it handles mouse-scrolling. Notepad (or rather the embedded EDIT control) works by detecting when the mouse leaving the confines of the window, but it only does this whilst the mouse is moving. As soon as the EDIT control stops receiving mouse-movement, the scrolling stops. So to achieve this very basic functionality we first add some code to our WM_MOUSEMOVE handler which can detect when the mouse is outside the TextView:

?
RECT rect;
POINT pt = { mx, my };
// get the non-scrolling area (an even no. of lines)
GetClientRect(m_hWnd, &rect);
rect.bottom -= rect.bottom % m_nLineHeight;
// detect where mouse is
if(PtInRect(&rect, pt) == FALSE) {
    // mouse is outside window, scroll in that direction

This basic method is not enough for a "grown-up" editor so we need to take this one step further. We will use a timer to generate regular scrolling events. However we won't just stop there - the other issue that we will look at is what to do about variable-speed scrolling. For example, many controls scroll their window-content slowly when the mouse is just outside the window, and speed up incrementally when the mouse gets further and further away. The picture below should help to illustrate this idea.

The inner red rectangle represents the client-area of the window. When the mouse is inside this region no scrolling is required. As the mouse move further and further away (represented by the red arrows) the window should be scrolled in that direction at the appropriate speed.

?
// If mouse is within client area, we don't need to scroll
if(PtInRect(&rect, pt))
{
    if(m_nScrollTimer != 0)
    {
        KillTimer(m_hWnd, m_nScrollTimer);
        m_nScrollTimer = 0;
    }
}
// If mouse is outside window, start a timer in
// order to generate regular scrolling intervals
else
{
    if(m_nScrollTimer == 0)
    {
        m_nScrollCounter = 0;
        m_nScrollTimer   = SetTimer(m_hWnd, 1, 10, 0);
    }
}

Variable speed scrolling

There are several methods which we can use to create variable-speed scrolling - however all methods are based around WM_TIMER and the SetTimer API. The first method is to reprogram the timer interval each time the mouse gets closer/further away from the window, resulting in a faster/slower rate of WM_TIMER messages being received by the window. When we receive a WM_TIMER, we scroll +1/-1 in whatever direction the mouse is. This is a little messy because it causes the timer to be reset each time SetTimer is called. See the following from excert MSDN on SetTimer :

"If the hWnd parameter is not NULL and the window specified by hWnd already has a timer with the value nIDEvent, then the existing timer is replaced by the new timer. When SetTimer replaces a timer, the timer is reset. Therefore, a message will be sent after the current time-out value elapses, but the previously set time-out value is ignored."

I'm unsure if this behaviour will cause us problems (i.e stuttering movement) or not. But the main reason I want to avoid this technique is that it doesn't support 2-dimensional scrolling very well. Imagine the following scenario: the mouse is held quite a distance from the top of the TextView (the vertical scrolling direction), but is only just outside on the left side (the horizontal direction). Which direction do we choose to base our scrolling speed on? The answer is, we can't have both fast scrolling (vertically) and slow scrolling (horizontally) at the same time with this technique.

The next method is to therefore use a constant timer interval set at a slowish rate. As the mouse moves further from the window (in either dimension), instead of speeding up the scrolling, the distance that is scrolled is increased - i.e. slow scrolling would be 1-line-at-a-time using the slow timer interval, faster scrolling would be 3-lines-at-a-time, and so on. This is a perfectly reasonable method and you can observe many controls using it for their scrolling.

The last method is similar to the previous one, but instead we program the timer to have a high repeat rate (i.e. 10ms). This fast interval allows us to scroll the window quickly when the mouse is at it's furthest from the window. And as we move the mouse closer, we can simply "skip" processing selected WM_TIMER messages. For example, we would process every WM_TIMER for full-speed scrolling, 1 out of 2 for half speed and 1 out of every 5 for very slow speeds. This results in smoother scrolling (we always scroll a line-at-a-time) but requires more CPU because more redrawing needs to be done. It has the advantage that we only ever scroll 1 line at a time so it is a little simpler from a coding and debugging point of view.

I opted for method#3 for Neatpad, simply because it was easier.

Avoiding flicker

Perhaps the reason that many text-editors exhibit flickering artifacts when they are scrolled up and down is because mouse-scrolling (in general) is so hard to get right. I do hate any sign of flickering though so it is very important from my point to view to ensure that Neatpad suffers no such problems.

There are two basic manifestations of "scroll flicker" that can be found in some applications. Both occur because the mouse selection and scrolling are not correctly synchronized. A lot of the time people don't notice these problems because most text files contain lines of varying length, and it isn't until you scroll a solid block of text that you begin to see what is going wrong. The animated gifs below hopefully illustrate the two problems - well, I hope they do because they took me ages to make! Once you've had enough of the animations just click the "stop" button in your browser window and they should stop.

The first flicker problem occurs because as the mouse moves outside of the window, the selection is redrawn, briefly extending outside of the window. Then the window is scrolled, bringing the end-of-selection back into view. It has the effect that the area to the left of the cursor appears to toggle between selected and unselected states. It's quite unsettling in my opinion but many controls exhibit this problem, including many standard Windows controls. We can prevent this problem by "clipping" the mouse coordinates to the edge of the window before we work out where the selection end-point should be.

The second case is almost the same as the first, except this time the scrolling happens first, which causes the whole display to scroll down. The selection is extended upwards after the scrolling, which again results in some fairly nasty flickering - this time because the unselected area on the first line is (wrongly) scrolled downwards and briefly appears as unselected text - then it is correctly repainted as highlighted. We can also prevent this problem by using clipping when we scroll the window - in this case, restricting the area that we scroll to a specific region which doesn't include the top/bottom lines.

It is not easy to get the mouse-selection and scrolling correctly synchronized. The basic problem is, we can't really treat these two actions as separate events because (obviously) they must occur at the same time. Adding to the problem is the fact that we have already written the scrolling and selection functionality, and it would be nice if we could still keep these as "separate" as possible so the code is kept clean. Let's have a brief recap of what we have so far:

  • TextView::Scroll(int dx, int dy)
    This routine scrolls the entire display in the specified direction, and also updates the scrollbar positions at the same time.
  • TextView::MouseCoordToFilePos(...)
    Retrieves the cursor position under the mouse, taking the scrollbar positions into account.
  • TextView::InvalidateRange(...)
    Redraws the specified range of text, also using scrollbar positions.

The basic strategy to synchronized mouse movement is outlined below.

  1. Work out the correct clipping region, based on the direction we are scrolling. i.e. if we are scrolling up, we exclude the top line from being scrolled. If we scroll left, we exclude the left-most column. This can all happen inside the existing TextView::Scroll routine.
  2. Scroll the window using ScrollWindowEx, but use the clipping rectangle worked out in step#1.
  3. Work out the new cursor position and selection end points. The scrolling had the effect of modifying the scrollbar positions (because we used TextView::Scroll), so we must work out the new cursor position after this scrolling has taken place.
  4. Redraw the region that wasn't scrolled (i.e. the area outside of the clipping rectangle).

In other words, all we are really doing is scrolling a sub-region of the window and then manually repainting the area we didn't scroll once the cursor/selection endpoint has been placed appropriately. How we fit all this together will determine how successful we will be.

ScrollWindowEx

Let's look at ScrollWindowEx so we know what scrolling facilities we have at our disposal:

?
int ScrollWindowEx(
  HWND hWnd,            // handle to window
  int dx,              // horizontal scrolling
  int dy,              // vertical scrolling
  RECT * prcScroll,       // client area              [optional]
  RECT * prcClip,         // clipping rectangle       [optional]
  HRGN hrgnUpdate,      // handle to update region  [optional]
  RECT * prcUpdate,       // invalidated region       [optional]
  UINT flags            // scrolling options
);

I have highlighted (in bold) the two optional parameters that we will be using - but first a brief recap of all parameters is probably appropriate at this stage:

  • hWnd is the window that we want to scroll. In our case this will be m_hWnd (the TextView!).
  • dx and dy specify the direction (in pixels) to scroll the window. These values can be positive or negative and can therefore be used to scroll in any direction.
  • prcScroll is a pointer to a RECT structure that defines the area to scroll. If this parameter is NULL then the entire client area is scrolled. Unless that is, ....
  • prcClip is a pointer to a RECT structure that defines a "clipping rectangle". This rectangle is the client coordinates of the window area that could be scrolled. No area outside of this rectangle will be affected by the scrolling.
  • hrgnUpdate is a HRGN (a handle-to-region). It is optional, but if specified, it must be initialized as a valid region (it doesn't matter what to). When ScrollWindowEx returns, hrgnUpdate represents the area of the window that has become invalid after the scrolling. This could be an irregular shape if we are scrolling in two dimensions at once.
  • prcUpdate is another RECT structure and receives the bounding rectangle of the update-region (above). It is not terribly useful because hrgnUpdate can be a COMPLEXREGION so a simple RECT structure cannot represent these regions accurately.
  • flags is a simple 32bit value. Usually this value is set to SW_INVALIDATE, to tell ScrollWindowEx to automatically invalidate the region that becomes invalid after the scroll, and add it to the window's update region. The next time the window processes WM_PAINT this invalid region is redrawn. This is the identical region that would be stored in hrgnUpdate (if this parameter was specified). However if we pass zero here, hrgnUpdate does not get invalidated, which would give us the chance to redraw this region later on if we so desired.

Due to the way we are scrolling (we always scroll away from the region we are protecting) it doesn't matter if we use the prcScroll or prcClip rectangles - both rectangles would hold the same values and the effect will be identical. In the sourcecode I have elected to use prcClip just because it is a little more obvious what it is being used for.

Scrolling Example

At this point we need to look at a scrolling example so that we are sure that we understand exactly what is happening with clipping rectangles, and update/invalid regions.

The picture above shows the Neatpad window before any scrolling has taken place. The Window is going to be scrolled upwards and left at the same time (i.e. -1, -1 in text-character-units). However, this means that we scroll the content down one line and right one character position (in order to expose new content at the top/left edges). In other words, the dx and dy parameters to ScrollWindowEx are positive.

Before the ScrollWindowEx function is invoked we define the clipping rectangle to be used. The cross-hatch shaded area represents content outside of the clipping rectangle - although this region is never invalidated or modified by the scrolling it is basically "dirty" and must be redrawn manually. The clipping rectangle we pass to ScrollWindowEx covers the clientarea of content/text that is not shaded. In this example, we took the top/left corner of the client-area and offset it to create the clipping rectangle.

The window after ScrollWindowEx. You should be able to notice that the content has scrolled down one line and right by one character. The cross-hatch/shaded area stays where it is because it is outside of the clipping rectangle. The inverted region represents the area that became invalid after the scrolling took place. I have chosen inverted colours purely to illustrate this area - in reality these pixels do not get modified by ScrollWindowEx and are only updated because the SW_INVALIDATE flag is specified.

Note: If we were using the hrgnUpdate parameter, this HRGN object would be modified to exactly fit the area represented by the inverted colours. It is quite an odd shape (an upside-down "L"), and for this example ScrollWindowEx returns COMPLEXREGION even though we're not using hrgnUpdate.

Both the cross-hatch and inverted regions need to be updated. For simplicity's sake we will use SW_INVALIDATE when we call ScrollWindowEx which will invalidate (and consequently update) the "inverted" region. However this leaves the cross-hatch region - we must manually create a HRGN which describes this area and call InvalidateRgn at some later point to redraw it. See below for how this is achieved.

Synchronized Scrolling

The first thing we must do is develop support for this "clipped" scrolling. I have rewritten the existing TextView::Scroll routine and called it TextView::ScrollRgn. It has an extra parameter now which specifies whether or not to return a handle to the region that is invalid after the scrolling (and indirectly controls the clipping behaviour).

When fReturnUpdateRgn is true, the scrolling is performed with the appropriate clipping, and a HRGN is returned to the caller. When fReturnUpdateRgn is false, the entire window is scrolled normally (i.e. with no clipping area defined).

?
HRGN TextView::ScrollRgn(int dx, int dy, bool fReturnUpdateRgn)
{
    RECT clip;
    GetClientRect(m_hWnd, &clip);
    // adjust the clipping rectangle fReturnUpdateRgn is false
    // do the scrolling
    ScrollWindowEx(m_hWnd,
                   -dx * m_nFontWidth,
                   -dy * m_nFontHeight,
                   NULL,                   // scroll the entire window
                   &clip,                  // clip the non-scrolling part
                   NULL,
                   NULL,
                   SW_INVALIDATE
                   );
    if(fReturnUpdateRgn)
    {
        RECT client;
        GetClientRect(m_hWnd, &client);
        HRGN hrgnClient  = CreateRectRgnIndirect(&client);
        HRGN hrgnUpdate  = CreateRectRgnIndirect(&clip);
        // create a region that represents the area outside the
        // clipping rectangle (i.e. the part that is never scrolled)
        CombineRgn(hrgnUpdate, hrgnClient, hrgnUpdate, RGN_XOR);
        DeleteObject(hrgnClient);
        return hrgnUpdate;
    }
    return NULL;
}

It is important that we preserve the existing TextView::Scroll functionality so we make this a wrapper function around TextView::ScrollRgn and specify false for fReturnUpdateRgn (i.e. make ScrollRgn scroll the entire window as normal).

?
VOID TextView::Scroll(int dx, int dy)
{
     ScrollRgn(dx, dy, false);
}

One thing I should mention is the units used by Scroll and ScrollRgn. These are always "text" units (i.e. line/character based) rather than pixel coordinates. The ScrollRgn function converts these to pixel coordinates when it is time to scroll. Also understand that when I write Scroll(-1, -1) this scrolls the document up/left - however the screen content is scrolled down/right to achieve this.

The function below is the "almost" full implementation of the TextView::OnTimer routine. The only part that has been omitted is the code that calculates what the values for dx and dy should be - its clearer without this going on so you can look in the sourcecode download to see how it is done.

?
LONG TextView::OnTimer()
{
    // [omitted] work out scrolling increments
    int dx, dy;
    // do the scroll but return the region to be manually painted
    HRGN hrgnUpdate = ScrollRgn(dx, dy, true);
    if(hrgnUpdate != NULL)
    {
        // do a "fake" WM_MOUSEMOVE to get the new cursor position
        OnMouseMove(0, mouse_x, mouse_y);
        // manually repaint the update region
        InvalidateRgn(m_hWnd, hrgnUpdate, FALSE);
        DeleteObject(hrgnUpdate);
        UpdateWindow(m_hWnd);
    }
}

The function fulfills all our criteria for smooth, synchronized scrolling. Firstly the window content is scrolled - however a HRGN is returned which specifies an invalid region that needs manually repainting. The text-caret and selection offsets are computed after doing the scroll (when the m_nxScrollPos variables become valid) - this is achieved by manually calling OnMouseMove and reusing the code that was already there. Just in case the mouse was moved whilst the timer went off, we also take advantage of the fact that OnMouseMove will also invalidate any area affected by selection change.

The very last thing to occur is to manually invalidate the region returned by ScrollRgn. The invalid window areas are finally repainted when UpdateWindow is called, using the updated cursor and selection offsets.

Neatpad additions

Most of this tutorial series will be focussed on the TextView component of Neatpad. I don't intend to cover the development of Neatpad in any great detail unless it is directly related to the support of the TextView. Instead I'll just give a brief mention of what has been added and let the readers study the sourcecode at their leisure.

This time around an Options dialog has been implemented which allows you to select the font and colours used by Neatpad. The dialog is fairly complete and the settings are saved to the registry each time Neatpad exits. The second options-pane doesn't do anything yet, but I have left it in as a "todo" which will be implemented at some point in the future. The code can be found in the Neatpad directory, in the Options.c and OptionsFont.c files.

A new message (TXM_SETCOLOR) has been added to the TextView in order to support programmatic control of colour settings:

?
#define TXM_SETCOLOR (TXM_BASE + 5)
// wParam = TXC_xxx index value
// lParam = RGB color#define TextView_SetColor(hwndTV, nIndex, rgbColor)

To send the message, use the TextView_SetColor macro. There are two parameters in addition to the window-handle. nIndex is a zero-based value taken from the TXC_xxx range of numbers. rgbColor is (you guessed it) a COLORREF RGB colour. For example, to set the selection background colour, use the following code:

?
TextView_SetColor(hwndTV, TXC_HIGHLIGHT, RGB(200,100,240));

There is one nice feature about this message which I hope people will like. If you want to set the colour to one of the predefined system colours (i.e. COLOR_WINDOWTEXT used with GetSysColor), then use the SYSCOL macro (defined in TextView.h):

?
TextView_SetColor(hwndTV, TXC_HIGHLIGHT, SYSCOL(COLOR_3DFACE));

The SYSCOL macro creates a "special" RGB value which the TextView recognises as a system-colour, not a plain RGB value. You only need to set the colour this way once, and subsequent changes in system colour schemes are automatically reflected in the TextView.

Coming up in Part 7

Hopefully I have given a good overview and explanation of how to scroll using the mouse-selection. Although it's not a particularly technical subject, it is fairly difficult to iron out the finer subtleties of mouse and timer interactions, and to be able to visualise these interactions whilst designing something like this (and then write about it!).

Onto the next tutorial then. Part 7 will be something a little simpler (I need to give my brain a rest!), so I will be implementing support for borders and margins. Margins will be provide us the ability to show line numbers and custom icons in a "selection" area (i.e. like Visual Studio uses for placing breakpoints). I also want to provide a "printer margin" on the right-side so that the printable area of a text document is distinguishable from any text that might get clipped due to printing.

You've probably noticed that so far all of the tutorials have been focussed around the graphical / user-interface aspects of Neatpad. This is a deliberate tactic as I feel it is important to have a good foundation before diving into complicated memory/file-management techniques. From experience I know it is easy to get distracted because of a half-finished GUI so I want to get all the GUI details completed.

‹ Mouse Selection & Highlighting up Margins and Long Lines ›

30 May 2005

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Margins and Long Lines | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
neatpad7.zip

Published on 22 Jan 2012

105.87 Kb

Download

Margins and Long Lines

Design & Implementation of a Win32 Text Editor

This will probably be quite a short tutorial as the subject of margins is really quite simple to implement. This time around we will look at implementing a selection margin (complete with full-line selection), line numbers, line-indicator icons (e.g. like the breakpoint bitmaps in Visual Studio), and lastly the problem of long-line display.

Drawing a margin

The main screenshot shows Neatpad with a selection-margin, line-numbers, bitmapped bookmarks and long-line highlights. Although it looks quite complicated drawing a margin is really easy, as long as you understand that it is just a rectangular area to the left of the display that is drawn and scrolled differently.

I've highlighted the picture above to illustrate that even though we've added a vertical margin, lines are still drawn horizontally one-by-one. However the line-drawing process (the TextView::PaintLine function) has changed to allow for the margins.

First of all the margin is drawn using the new function TextView::PaintMargin

?
int TextView::PaintMargin(HDC hdc, ULONG nLineNo, RECT *margin)

The area taken by the margin is then clipped to prevent anything drawing over it. Next, the text is drawn as normal - but offset to the right of the margin. The clipping is important because it ensures that when we scroll horizontally the lines of text don't overdraw the margin and instead "disappear" behind the margin. Basically the process looks like this:

?
void TextView::PaintLine(HDC hdc, ULONG nLineNo)
{
    RECT rect;
    // work out where to draw the line// handle the margins
    if(LeftMarginWidth() > 0)
    {
        RECT margin;    // work out the margin coordinates
        // paint the margin
        PaintMargin(hdc, nLineNo, &margin);
        // clip the margin so the text doesn't draw over it
        ExcludeClipRect(hdc, margin.left, margin.top, margin.right, margin.bottom);
        // offset the text placement
        rect.left += LeftMarginWidth();
    }
    // paint the text as normal
    PaintText(hdc, nLineNo, &rect);
}

The key point to understand is that the normal line-text must be offset to the right to take into account the margin. It's really very simple so just take a look at the sourcecode download to see it in action.

Two more important things to mention: I have also had to modify the scrolling routine so that the margin is not scrolled when the main text is scrolled. This was very simple as the clipping rectangle we specify in ScrollWindowEx is simply adjusted so that the margin does not get included.

?
HRGN TextView::ScrollRgn(int dx, int dy, bool fReturnUpdateRgn)
{
    ...
    // take margin into account
    clip.left += LeftMarginWidth();

    ...
}

The other modification was the mouse-input (selection) code. The cursor-position must be adjusted so that the margin is again taken into account. I really don't want to include any code for this as you should be getting the idea by now. Basically, any x-coordinate is shifted to the right by the size of the margin before any input or drawing takes place.

Line bitmaps

Just a quick word about the line-indicator bitmaps you can see. I've added a new ImageList member variable to the TextView class which can hold a user-specified collection of bitmaps:

?
HIMAGELIST m_hImageList;

The image-list can be set using the new TextView TXM_SETIMAGELIST message (or the TextView_SetImageList macro).

?
HIMAGELIST hImageList = ImageList_LoadImage(...);
TextView_SetImageList(hwndTextView, hImageList);

Using an image-list is the simplest way to work with images as it manages the bitmap memory, and also provides drawing support as well - so storing/drawing bitmaps is really easy.

The difficult part comes when we draw each line. How do we know what image to place in the margin-area? For a text-editors which use an array or linked-list of lines this is really easy - because additional information can be easily stored for each line entry in the editor. However for our Neatpad design this is not possible because we have to support large-file editing at some point - and having a line-buffer for a 4gb file would not be a good idea.

For this reason I have used a separate array which holds only those lines that have bitmaps associated with them. The array is always sorted so that we can use a binary-search to quickly determine if a specific line has any bitmap assocated with it.

?
typedef struct
{
    ULONG nLineNo;
    ULONG nImageIdx;
} LINEINFO;LINEINFO m_LineInfo[MAX_LINE_INFO];

Each time a line is drawn the LINEINFO array is searched using the TextView::GetLineInfo function:

?
LINEINFO* TextView::GetLineInfo(ULONG nLineNo)
{
    LINEINFO key = { nLineNo, 0 };
    // perform the binary search
    return (LINEINFO *) bsearch(
                          &key,
                          m_LineInfo,
                          m_nLineInfoCount,
                          sizeof(LINEINFO),
                          (COMPAREPROC)CompareLineInfo
                       );
}

This function returns a pointer to the appropriate LINEINFO structure if successful, or NULL if there is no stored information for the specified line.

I anticipate that more information could be stored about lines such as a whole-line highlight colour, bookmarks, annotations etc. However for now I have just included support for an image-index. The images for each line can be set using another new TextView message, TXM_SETLINEIMAGE:

?
TextView_SetLineImage(hwndTextView, nLineNo, nImageIdx);

Line-selection and mouse input

Now that we have a margin to the left of the main text display we can use this area to initiate line-based selections.

To allow for this line-based selection method, we need to keep track of more than just the fact that we are making a selection. The previous boolean m_fSelection (which was just used to indicate if a selection was in progress or not) has been replaced by a new variable:

?
SELMODE m_nSelectionMode;

m_nSelectionMode is a SELMODE enumeration with the following values:

?
enum SELMODE
{
   SELMODE_NONE,
   SELMODE_NORMAL,
   SELMODE_MARGIN
};

So at the moment we support two types of selection - "normal" and "margin". At some point in the future this could be extended to support other types of selection such as column and block selections. Of course the mouse-routines have been modified to understand the new types of selection.

Highlighting long lines

One feature which I find particularly useful is the highlighting of long lines. I can still remember the Borland C++ 4.0 IDE for Windows 3.1 which featured a single vertical-line margin to indicate where column-80 was. I actually found this a little tacky but I wanted to create a similar effect.

The reason for highlighting longs lines is provide a way to indicate to the user when a line of text becomes too long. This is most useful for programmers who don't want their text to "wrap" when it is printed out - so the aim is to keep all lines of text under a certain limit.

There are basically two ways of implementing long-line highlights:

  • Use a two-stage approach. The first stage prepares the background using two colours to distinguish "normal" text on the left and "clipped" text on the right. i.e. A fixed-sized area is filled using the normal background colour, up to (say) column-position 80. The background after this is either filled using a different shade, or a vertical line is drawn to indicate the long-line limit.

    The text is then drawn over the top of this background in transparent mode - so the background shows through. This is really a "pixel-based" approach because the text drawing is independent of the long-line display.

  • The alternative method is to simply change the background-colour of any character that is drawn past column 80. This is what the Scintilla text-editor component does and is alot simpler than the method above - so this is what I've implemented inside Neatpad.

In order to support this new long-line display I've had to extend the TextView::ApplyTextAttributes member-function with a new parameter - &nColumn :

?
int TextView::ApplyTextAttributes(ULONG nLineNo, ULONG nOffset, ULONG &nColumn, TCHAR *szText, int nTextLen, ATTR *attr)

Notice that nColumn is a C++ reference. It is continually updated by ApplyTextAttributes to keep track of the current column-position (we need a C++ reference so that the value is preserved through successive calls). Once nColumn reaches a certain value, ApplyTextAttributes will use a different default background colour for the text.

A new message (TXM_SETLONGLINE) has been added to the TextView to allow the long-line limit to be programmatically altered. The TextView_SetLongLine can be used to set this value:

?
TextView_SetLongLine(hwndTextView, 80);

64bit support

This probably a little late in the day (it should have happened from day#1) but I've started to make the Neatpad and TextView projects 64bit compatible. You will therefore need to use a recent Platform SDK when compiling Neatpad in order to get the new mixed 32bit/64bit definitions. This has also resulted so far in the following change:

  • Get/SetWindowLong has changed to Get/SetWindowLongPtr

To test that these changes actually worked I compiled the project using the Microsoft 64bit compiler, targetted for the IA64 architecture. Follow the steps below to duplicate this:

  • Export the Neatpad project as a makefile:Project -> Export Makefile
  • Open the Platform SDK 64bit build environment from the start-menu:Microsoft Platform SDK -> Windows XP 64bit Build Environment
  • Change to the Neatpad project directory and open Neatpad.mak\Neatpad07\Neatpad
  • Modify the LINK32_FLAGS so that both the /machine switches reads "IA64" instead of "I386", and remove any reference to ODBC32.LIB and ODBCCP32.LIB
  • Make sure that the Neatpad project has been "cleaned" then build using the 64bit tools:nmake Neatpad.mak

There are still alot of changes to make (ULONG vs ULONG64 issues) and as I don't have access to a 64bit machine currently I won't be able to make any more modifications until I can test properly. However I hope to have Neatpad fully 64bit compatible before the end of the series!

Conclusion

This tutorial was a little off-track as margins and long-line highlights are not really that important for a text-editor's design. Anyhow I wanted to get it out of the way so I could concentrate on the core functionality.

Coming up in Part 8 will be support for UTF-8 and Unicode!

‹ Scrolling with the Mouse up Introduction to Unicode ›

7 Aug 2005

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Introduction to Unicode | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Introduction to Unicode

Design & Implementation of a Win32 Text Editor

The user-interface for the TextView has progressed enough to allow us to switch our attention back to the TextDocument. I am now concentrating on adding full Unicode support to Neatpad. Because Unicode is such a complicated subject I won't attempt to tackle it all at once so instead I will split the various aspects of Unicode across several tutorials.

The first Unicode topic (Part 8 - what you are reading now) will be an introduction to Unicode and the various encoding schemes that are in common use. There won't be any code download as this will be purely a discussion about Unicode to make sure everyone understands the various issues. The next Unicode topic (Part 9) will look at how to incorporate the concepts discussed here into Neatpad - so will focus on loading, storing and processing Unicode data. The rest of the Unicode topics will focus on the issues surrounding the display of Unicode text - and will include complex script support, bi-directional text and the Uniscribe API.

Unicode Myths

Before we start properly it may be worth dispelling the most common myths about Unicode - and hopefully by the end of this article you will have a good idea about what Unicode is all about.

The most common incorrect statement I see about Unicode is this: "In Unicode all characters are two bytes long."

This is totally incorrect. The Unicode standard has always defined more than one encoding form for its characters - with UCS-2 originally being the most common. However since Unicode 2.0 there no longer exists any encoding scheme which can represent all characters using two bytes (read about UTF-16 further down the page).

It doesn't help that even Microsoft gets it wrong in it's own documentation sometimes with statements such as "Unicode is a wide-character set". Well, they're half-way right - on Windows at least, Unicode strings are typically encoded as UCS-2/UTF-16, but it is quite misleading to claim that Unicode is a "wide-character-set" because it is so much more than that.

The next most common question you hear Windows programmers asking is "How do I convert a UNICODE string to a UTF-8 string?". This is a question usually asked by somebody who doesn't understand Unicode. A UTF-8 string is a Unicode string so it cannot be converted. Probably the person asking the question meant "how to I convert between this UCS-2 formatted string and UTF-8?". Of course, the answer in this case would be the WideCharToMultiByte() API call.

One last misconception is that the wchar_t "wide character" type is 16bits. This is maybe true on Windows platforms, but the C language makes no such assumption about the width of a wchar_t type - it can be as wide as the C compiler wants it to be in order to represent a single "wide character", and on UNIX and Linux wchar_t is commonly a 32bit quantity.

Code Pages and Character Sets

Everyone is familiar with the ASCII character-set, which encodes 128 unique character values (0-127) using 7bit integers. Most people are also aware of the existance of the ANSI character-set(s), which use a full 8bit byte to encode 256 (0-255) character values. And it is probably fair to say, most people are fully aware that an 8bit byte is not sufficient to encode all of the world's writing systems, except maybe a very few European languages.

These "byte-based" character sets are often referred to as Single-Byte-Character-Sets, or SBCS for short. Most of the 8bit character-sets keep the bottom 128 characters as ASCII, and define their own characters in the top "half" of the byte. There are many, many single-byte character-sets in existence.

These extra character-sets are referred to as codepages (a traditional IBM term), and are each identified by a unique codepage number which is usually defined by the ANSI/ISO organisations. For example, the familiar ANSI codepage used by Windows is 1252. A Windows application could set it's codepage number to tell Windows from which character-set it wanted to work with.

Of course a single 8bit character was never going to be enough to represent the rest of the world's writing systems. The east-Asian languages especially needed a different approach and this is where Double-Byte-Character-Sets (DBCS) come into play. With these character-sets, a character can be represented by either one or two bytes. There are many other character-sets which share this design and Microsoft refers to them all as Multi-Byte-Character-Sets (MBCS) in it's documentation. The one thing all these character sets have in common is their complexity - they are all quite difficult to work with from a programming perspective.

You may be familar with the many APIs and support libraries for dealing with MBCS strings such as CharNext, CharPrev, _mbsinc, _mbsdec etc. All of these APIs are designed for a program to work with legacy character-sets - and rely on the correct codepage to be setup before an application can display text correctly.

Note that all of these concepts are really quite out of date now. SBCS, MBCS and DBCS, and the whole idea of codepages all belong in the past and thankfully we no longer have to worry about them.

What is Unicode?

There seems to be alot of confusion surrounding Unicode. This is mostly due to the fact that Unicode has evolved quite significantly since its first release in 1991. A great deal of information has been written about Unicode during this time and much of the earlier information is now inaccurate. Almost 15 years later Unicode is now at version 4.1 - and your perception of Unicode has probably been most influenced depending on when you first became exposed to the subject. Understanding the history of Unicode is almost as important as understanding Unicode itself.

Unicode is the universal character encoding standard for representing the world's writing systems in computer-form. It is also a practical implementation of the ISO-10646 standard. The Unicode consortium (represented by several international companies including Microsoft and IBM) develops the Unicode standard in parallel with ISO-10646. Often you will see terms from each standard used interchangably but really they refer to the same thing.

The main purpose behind Unicode is to define a singlecode-page which holds all of the characters commonly in use today. At its heart Unicode is really just a big table which assigns a unique number to each character as defined by ISO-10646. Each of these numbers in the Unicode codepage is referred to as a "code-point". The following are examples of Unicode code-points:

U+0041 "Latin Capital Letter A"U+03BE "Greek Small Letter Xi"U+1D176 "Musical Symbol End Tie"

The standard convention is to write "U+" followed by a hexadecimal number which represents the codepoint value. Often you will also see a descriptive tag next to each code-point, which gives the full name to the codepoint as defined in the Unicode standard.

The Unicode standard can represent a little over one million code-points. With version 4.0 around 96,382 characters have been assigned to actual code-points, leaving approximately 91% of the encoding space unallocated. With most of the world's writing systems already encoded (including the garantuam Chinese-Japenese-Korean character-sets), this leaves a lot of expansion for future use.

A single code-point within this encoding space can take a value anywhere between 0x000000 and 0x10FFFF. Unicode codepoints can therefore be represented using 21-bit integer values. It is no accident that these numbers were chosen and if you read into the UTF-16 format more deeply you will understand why Unicode has been limited in this way. It is important to note that both the Unicode consortium and ISO pledge to never extend the encoding-space past this range.

UTF-32 and UCS4

Of course, a 21-bit integer is a bit of an "odd" sized unit and doesn't lend itself well to storage in a computer. As a result of this, Unicode defines several TransformationFormats with which to encode streams of Unicode code-points. The three most common are "UTF-8", "UTF-16" and "UTF-32".

Out of these three, UTF-32 is by far the easiest to work with. Exactly one 32-bit integer is required to store each Unicode character. However, UTF-32 is very wasteful - 11 bits out of the 32 are never used, and in the case of plain English text encoded as UTF-32, this means 75% waste overall. The table below illustrates how a 21bit integer (represented with 'x's) is encoded in a 32bit storage unit:

Unicode | UTF-32
00000000 - 0010FFFF | 00000000-000xxxxx-xxxxxxxx-xxxxxxxx

Some operating systems (like UNIX variants) use UTF-32 internally to process and store strings of text. However UTF-32 is rarely used to transmit and store text-files simply because it is so space-inefficient and because of this, it is not a commonly encountered format.

UCS-2

UCS is a term defined by ISO-10646 and stands for Universal Character Set. When Unicode was first released the primary encoding scheme was intended to be the UCS-2 format. UCS-2 uses a 16bit code-unit to store and represent each character. At the time this was considered an adequate scheme because only 55,000 characters had been assigned to Unicode code-points thus far - this meant that every Unicode character (at the time) could be represented by a single 16bit integer. Unfortunately we are still paying the consequences for this incredibly short-sighted decision.

Even before Unicode was developed there existed many "wide character sets" which required more than one byte to store each character. The most notable were IBM's DBCS (double-byte-character-set), JIS-208, SJIS and EUC to name just a few. To support the various wide-character sets, the wchar_t type was introduced into the C standard in the late 80s (although it wasn't ratified until 1995). The wchar_t type (and wide-character support in general) provided the mechanism to support these wide-character sets.

The companies which backed the UCS-2 format pledged support for Unicode. Microsoft in particular engineered its Windows NT OS line to be natively "Unicode" compatible right from the outside, and used the 16bit UCS-2 wide-character strings to store and process all text.

UTF-16

In 1996 Unicode 2.0 was released, extending the code-space beyond 65,535 characters - or what is known the Basic Multilingual Plane (BMP). It was obvious that a single 16bit integer was insufficient to encode the entire Unicode code-space, and the UTF-16 format was introduced along with the UTF-16 SurrogateMechanism. Importantly, UTF-16 is backward compatible with UCS-2 (it encodes the same values in the same way).

In order to represent characters from 0x10000 to 0x10FFFF, two 16bit values are now required - which together are called a Surrogate Pair. This also means that there is no longer a 1:1 mapping between the 16bit units and Unicode characters. The two 16bit values must be carefully formatted to indicate that they are surrogates:

  • The first 16bit value is called the "high surrogate" and must have the top 6 (of 16) bits set to "110110". This leaves 10 unused bits for storing values from 0xD800 to 0xDBFF, providing a range of 1024 characters.
  • The second 16bit of the pair is called the "low surrogate" and must have the top 6 bits set to "110111" - this results in values from 0xDC00 to 0xDFFF, again providing a range of 1024 characters.

This "surrogate range" between D800 and DFFF was "stolen" from the one of the previously named "Private Use Areas" of UCS-2. When combined together a surrogate-pair provides 1024x1024 combinations, which results in 0x100000 (1,048,576) additional codepoints outside of the BMP. The table below illustrates how the Unicode code-space is represented using UTF-16.

Unicode | UTF-16
00000000 - 0000FFFF | xxxxxxxx-xxxxxxxx
00010000 - 0010FFFF | 110110yy-yyxxxxxx 110111xx-xxxxxxxx

So in essence, UTF-16 is a variable-width encoding scheme much like the multi-byte UTF-8. You may be wondering (as I did) exactly what the advantage is now that UTF-16 is no longer a fixed-width format. It would be interesting to see if UTF-16 would be in use today had UTF-8 been available right from the start.

Even without the variable-width problem, using UTF-16 from a C/C++ perspective is pretty tiresome because of the strange wchar_t type and the L"" syntax for widecharacter string literals. Understand that UTF-16 is the dominent encoding format at the moment. Microsoft Windows and Macintosh OSX both use it for their operating systems, and the Java and C# languages also use UTF-16 for all string operations. UTF-16 is unlikely to go away any time soon.

Actually, the variable-width nature of UTF-16 and the slight complexity it now brings pales into insignificance when compared with the nightmare of displaying Unicode. It really doesn't matter that a string is in multibyte format - even with UTF-32 one codepoint does not necessarily map to one visible/selectable "glyph", as we will find out over the next parts of this series.

UTF-8

One very popular encoding format is UTF-8, officially presented in 1993. A common misconception is that UTF-8 is a "lesser" form of UTF-16. Nothing could be further from the truth - it encodes the exact same Unicode values as UTF-16 and UTF-32, but instead uses variable-length sequences of up to four 8-bit bytes. This means that UTF-8 is a true multi-byte format. Much of the text on the Internet (such as in web pages and XML) is transmitted using UTF-8, and many Linux and UNIX variants use UTF-8 internally.

The way UTF-8 works is quite clever. The MSB (most-significant-bit) in each byte is used to indicate whether a character-unit is a single 7bit ASCII value (top bit set to "0"), or is part of a multibyte sequence (top bit set to "1"). This means that UTF-8 is 100% backward compatible with plain 7bit ASCII text - of course, it was designed for this very purpose. The design allows older non-Unicode software to handle and process Unicode data with little or no modification.

There are actually three basic constructs in UTF-8 text:

  • Plain ASCII text (characters in the range 0-127) is represented as-is with no modification.
  • Lead-bytes identify the start of a multibyte sequence. The number of "1" bits at the top of a lead-byte denote how many bytes there are in the sequence, including the lead-byte. So a byte with "110" at the start denotes a 2-byte sequence and a byte with "1110" denotes a 3-byte sequence, and so-on. The remaining bits at the bottom of the lead-byte are used to store the first part of the 21-bit Unicode value.
  • Trail-bytes always start with "10" as the top-bits, with the lower 6 bits being used to store the remaining bits from the Unicode value. A trail-byte must always follow a lead-byte - it cannot ever appear on its own.

So, a Unicode value in the range 0-127 is represented as-is. Values outside of this range (0x80 - 0x10FFFF) are represented using a multibyte sequence, comprising exactly one lead-byte and one-or-more trail-bytes. Each Unicode character with a value above 0x7F has it's bits distributed over the "spare" bits in the multibyte sequence.

The following table illustrates this concept:

Unicode | UTF-8
00000000 - 0000007F | 0xxxxxxxx
00000080 - 000007FF | 110xxxxx 10xxxxxx
00000800 - 0000FFFF | 1110xxxx 10xxxxxx 10xxxxxx
00010000 - 001FFFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
00200000 - 03FFFFFF* | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
04000000 - 7FFFFFFF* | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

Notice that the last two rows have been marked with an asterisk: they are illegal forms of UTF-8. Although UTF-8 can theoretically be used to encode a full 31bit integer using lead-bytes 111110xx and 1111110x, these are overlong sequences because they represent numbers outside of the 0-10FFFF unicode range. Remember that this "artificial" limit has been imposed due to the UTF-16 surrogate mechanism.

Unicode text files

If you have ever used the regular Notepad on Windows NT you may be aware that text files can be saved in several formats - ASCII, Unicode (which is really UTF-16), Unicode-Big-Endian (which is big-endian-UTF-16) and lastly UTF-8.

The problem with text-files on Windows (and probably most other operating-systems) is that there is no way to tell what type of text is contained within a file - because plain-text files (by their very nature) provide no such facility. The Unicode standard therefore defines a method for tagging a text-file with a "Byte Order Mark" in order to identify the encoding scheme used to save the file. The optional "BOM" sequences are listed below.

Byte Signature | Unicode Format
none | Plain ASCII/ANSI
EF BB BF | UTF-8
FF FE | UTF-16, little-endian
FE FF | UTF-16, big-endian
FF FE 00 00 | UTF-32, little-endian
00 00 FE FF | UTF-32, big-endian

The table above was taken from the Unicode Standard 4.0. The BOM values are chosen because it would be extremely unlikely to ever encounter those character-sequences at the start of a plain-text document. Of course it is still possible to encounter such files - its just very rare.

With Neatpad, in the absence of any signature the file is treated as plain ANSI text. This is in contrast to how Notepad works - it uses statistical analysis of the file in order to make a 'best guess' as to the underlying format, and sometimes gets it wrong.

Relevant reading

The first place you should start is www.unicode.org/faq. This is the official site for Unicode and contains the complete Unicode 4.1 standard. The standard is also available in book (hardback) form.

However if you want a really good book on Unicode then I can recommend "Unicode Demystified" by Richard Gillam. This book gives a really good practical coverage of the many Unicode issues and I found it indispensible whilst researching this project.

The following links may also prove to be useful:

  • WIKI page on Unicode
  • UTF16 for processing
  • Characters vs Bytes
  • Forms of Unicode
  • Power user's guide to multilingual text editors
  • A tutorial on character-code issues
  • UTF-8 and Unicode FAQ for Unix/Linux

Unicode C++ projects in Windows

Seeing as we want to support Unicode in Neatpad, it makes sense for us to use the native Unicode support provided by the Windows operating systems. In practise this means using the "Wide-character" Unicode APIs - which are basically UTF-16/UCS-2. There is a certain technique to writing Unicode-enabled applications under Windows which every programmer should be aware of.

  1. The first step in creating any Unicode Windows project is to enable support for the wide-character APIs. This is usually achieved by defining the UNICODE and _UNICODE macros for every source-file in your project (and removing macros such as _MBCS and _DBCS). The reason two macros are required is simple: UNICODE is used for the Windows/Platform SDK libraries, whereas _UNICODE is used for the standard C/C++ runtime libaries.
  2. The second step is to #include <tchar.h> - this file contains many "support macros" that are very useful for Unicode projects.
  3. The third step is to define any character type as TCHAR - this is another macro and results in WCHAR string types for UNICODE projects, and char string types for regular "non Unicode" projects.
  4. The forth step is to declare all string literals using the _T and _TEXT macros, which are defined in <tchar.h>. These macros control how string-literals are defined. For a non-Unicode project these macros do nothing, however for a UNICODE project all string-literals have the L"" string prefix attached.
  5. The fith and final step is replace all calls to C-runtime string functions (such as strcpy) with their _tcs equivalent (e.g. _tcscpy). Although these equivalents can all be found in the <tchar.h> runtime header, there is a simple trick to obtaining the '_t' name from the original - just replace the 'str' part with '_tcs'.

You've hopefully got the idea that Unicode programming in Windows relies heavily on the C/C++ preprocessor for support. The example below illustrates all of these concepts together.

?
#include <windows.h>
#include <tchar.h>
TCHAR szFileName[MAX_PATH];
// calling one of the standard-C calls
_tcscpy(szFileName, _T("file.txt"));
// calling one of the Platform-SDK APIs
CreateFile(szFileName, GENERIC_READ, ...);

Because TCHAR, _tcscpy, _T and CreateFile are really MACROs, with UNICODE defined our sample program becomes:

?
WCHAR szFileName[MAX_PATH];
_wcscpy(szFileName, L"file.txt");
CreateFileW(szFileName, ...);

Note that the WCHAR character-type is actually another macro which is defined as wchar_t. The Visual-C compiler treats this wide-character type as a 16bit quantity. Don't assume that this is true across all platforms - for example on UNIX systems wchar_t is usually a 32bit quantity because the native Unicode format is UTF-32 on these systems.

Without the UNICODE setting defined our sample program becomes an ordinary "C" program:

?
char szFileName[MAX_PATH];
strcpy(szFileName, "file.txt");
CreateFileA(szFileName, ...);

Rather than putting UNICODE and _UNICODE at the top of every source-file we will make things easier on ourselves. The last thing we need to do is configure the Neatpad and TextView projects to build as Unicode applications on a project-wide basis. Rather than modify the existing projects, we will add two new project configurations (one for Debug and one for Release). This will allow us to build the an ASCII-only Neatpad, and a Unicode build from the same sourcecode.

Select Build -> Configurations menu item from Visual Studio.

The new configurations are created by using the existing non-unicode projects as templates. We need to perform this task for both the Neatpad and TextView projects, and for each Debug and Release build as well. Once done we will have four project configurations for each project - Debug, Release, Unicode Debug and Unicode Release.

Coming up in Part 9

Hopefully this has been a useful introduction to Unicode. I felt it was necessary to cover the basics first before diving straight in as Unicode is such a complicated subject. The next part of this series will look at taking the ideas presented here and integrating directly into Neatpad.

‹ Margins and Long Lines up Unicode Text Processing ›

4 Dec 2005

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Unicode Text Processing | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
neatpad9.zip

Published on 22 Jan 2012

147.57 Kb

Download

Unicode Text Processing

Design & Implementation of a Win32 Text Editor

The last tutorial presented an overview of the various encoding formats that are used to store Unicode text. It is now time to take this theory and apply it to Neatpad. Therefore the subject of this article will be Unicode text processing.

The image above shows Neatpad's new Encoding menu option - with a UTF-8 file displayed in all it's glory. At the top of this tutorial are a collection of Unicode files which you can use to test Neatpad's Unicode capability.

Loading text files

Previous incarnations of Neatpad supported a single text encoding - plain ASCII text. A Unicode text editor must naturally support the various Unicode file-formats so our first step will be to modify the TextDocument's init() function to detect what type of file we are opening.

Of course it isn't possible to detect what type of encoding a text-file uses until we actually open the file and read the first few bytes. We will use what Unicode terms the "Byte Order Mark" - a specific sequence of bytes that can only appear at the start of a Unicode text file, and if present will determine the exact encoding method used to save the file.

Byte Signature | Unicode Format | Neatpad Format
none | Plain ASCII/ANSI | NCP_ASCII
EF BB BF | UTF-8 | NCP_UTF8
FF FE | UTF-16, little-endian | NCP_UTF16
FE FF | UTF-16, big-endian | NCP_UTF16BE
FF FE 00 00 | UTF-32, little-endian | NCP_UTF32
00 00 FE FF | UTF-32, big-endian | NCP_UTF32BE

Therefore a new function has been added to the TextDocument - TextDocument::detect_file_format - who's purpose is to detect the format of the text-file as it is being loaded during TextDocument::init. In the absence of any file-signature we will assume that the file-contents is plain ASCII/ANSI text.

?
int TextDocument::detect_file_format(int *headersize);

This function's sole task is to analyse the first x bytes of a file and compare them against the various Byte-Order-Mark values that are defined in the table above. It is literally a task of performing a series of memcmp 's until we match a format. The detect_file_format function returns an appropriate NCP_xxx value (Neatpad Codepage) to indicate what type of file is being processed.

The file's text-format is stored internally by the TextDocument (in member-variable 'fileformat '). The length of the Byte-Order-Mark header is also saved away in the 'headersize' member-variable - so that we can always identify the start of the real content no matter what type of file we are loading.

Internal text representations

Most text-editors (such as Notepad) will load an entire text-file into memory. No matter what the underlying file format (i.e. ASCII, UTF-8 or UTF-16), the contents will be converted to an internal format to make it easier to work with. For Windows programs, this is usually (but not always) the native UTF-16 format of Windows NT. This makes sense because all of the text-based Windows APIs are designed to handle UTF-16/UCS2.

This is a great way to structure a program because you maintain one set of source-code for the main editor (which interfaces directly with the OS's text routines), and then write a set of simple file I/O conversion routines which load and save each of your supported formats. The editor is kept very simple because the text it processes is always in one format. When it comes back to saving a file in it's original format then the entire text is converted back again.

Of course this method could require large amounts of memory because the entire file must be loaded at one time. In order for us to support our goal of a multi-gigabyte text editor we must leave the file in it's "raw" state and only map specific parts of a file into memory as required - much like the HexEdit program on this site.

However this leaves us with a problem - how do we handle many different forms of text within the same program but still keep a single code-base which is not over-complicated by the various encodings it must process? Here are the two basic strategies available:

  1. Write separate versions of the TextView/TextDocument for each specific file format. We would then create a specific instance of TextView (i.e. TextViewUtf8 / TextViewUtf16) depending on what type of file we encountered. We could potentially use macro's / C++ templates to make our lives easier but I believe this method will be a code-maintanence-nightmare. Avoid at all costs!
  2. Write a generic TextView which always handles text in the "native" format (i.e. UTF-16 for Windows). The TextView would have no knowledge of the underlying file-format, and it would be up to the TextDocument to convert the underlying file-format into UTF-16 as the TextView requested.

I think that method#2 will provide the greatest flexibility and with careful design should work well for Neatpad.

Generic text processing

The idea behind a "generic" design is that the TextView always gets to see and process UTF-16 text (i.e. standard wide-character Unicode strings). It is completely unaware that the underlying file the TextDocument is reading is anything other than UTF-16 text. This means that whenever the TextView asks for text to display, it is up to the TextDocument to translate (if necessary) the underlying file contents into UTF-16 (i.e. on the fly in realtime).

The TextDocument on the other hand understands "all" types of file-format. It knows how to read the various encodings that we will support - so this would be ASCII, UTF-8 and UTF-16.

I feel that this type of design will suit Neatpad very well. Because the user-interface (the TextView) has the potential to be so complicated, it is very important to try and isolate all of the text-conversion problems into one place so that we only have to worry about it once. It also has the advantage that we could add further text-formats to the TextDocument (i.e. UTF-32) and the TextView would never have to be modified. The TextView should only care about UTF-16.

Two coordinate systems

Deciding to move to this "generic" text model has introduced a major problem, because we now have two coordinate systems to consider - one for the TextView, and one for the TextDocument. At this point we could just say "we'll support UTF-16 for the moment and add UTF-8 later on" - but this would be a mistake. The design of a "single format" editor is very different to an editor that must handle arbitrary file-formats and we must move to a more generic design or this will cause us even bigger problems later on.

So, we have decided that the TextView will work exclusively in UTF-16 units. This is a good thing. It basically means that the entire "user-interface" to the TextView control is in the Native Windows Unicode format. Don't underestimate how important this is. We haven't progressed this far yet, but try to imagine a "user" of the TextView control (i.e. a programmer) using it in a C++ project:

This programmer's project will naturally be Unicode and all text operations will therefore also be UTF-16. TextView operations such as cursor positioning, selection management, getting and setting text at specific offsets, searching for text etc must be UTF-16 also. The user/programmer doesn't care what the underlying format of the textfile is, all they see of the world is UTF-16, and all operations must match this view of the world. Therefore our cursor offsets and selection offsets - our entire coordinate "front end" to the control - must be UTF-16 based. This is where we hit our problem though:

The TextDocument has a different view of things. It must work with arbitrary file formats and it won't know - until loaded - what format a text-file will be in. It could be dealing in single-byte formats (ASCII), multi-byte formats (UTF-8) or wide-character formats (UTF-16). The TextDocument must use a coordinate-system that is common to all these formats. Of course this will be a byte-oriented system - so all line-offsets and text-accesses will must be byte-based.

To try and illustrate this TextView/TextDocument divide, let's look at a quick example of some Unicode text:

?
U+0041  LATIN CAPITAL LETTER
U+06AF  ARABIC LETTER GAF
U+16D4  RUNIC LETTER DOTTED-P
U+10416 DESERAT CAPITAL LETTER JEE

The text above is just a random collection of four Unicode characters - with the Unicode code-point values listed to the side. To see how these characters relate to Neatpad, we will imagine that the text above has been encoded as UTF-8 and loaded into Neatpad. The TextDocument would therefore be working in UTF-8 multi-byte units:

The TextView of course sees the file as UTF-16. Hopefully the diagram illustrates just how separated the TextView has become from the underlying file. Apart from the first character ('A'), the raw data that the TextView gets to see is completely different to how it really appears on disk. Remember, all this should be happening in realtime, not during the file-loading process.

But we still haven't solved our problem. The TextView speaks UTF-16 and the TextDocument speaks byte-offsets. We need to devise some kind of mechanism to perform mappings between UTF-16 offsets (i.e. code-unit offsets) and the underlying file-content (whatever that may be). This task will fall to the TextDocument, and it will be the line-offset buffer that will be doing all the hard work.

Reading Unicode data

The decision to make the TextView UTF-16 only means that our TextDocument::getline routine must change. Remember that this is the main "gateway" between the TextView and the TextDocument. Let's look at what we had before:

?
ULONG TextDocument::getline(ULONG lineno, ULONG offset, char *buf, size_t len, ULONG *fileoff)

The TextDocument::getline routine basically returns a block of text from the specified line - and always returns this text as plain ANSI. Two things are going to change here. Obviously the text-type must change from char* to wchar_* if we want to support Unicode. This change has been achieved by converting all char* types to TCHAR* on a project-wide basis and creating a separate Unicode Build.

The second change is to move away from a line-oriented text-retrieval model. What we have now is a getline replacement - called TextDocument::gettext. The purpose of this new routine is to return UTF-16 text from the specified byte offset within the current file:

?
int TextDocument::gettext(ULONG offset, ULONG maxbytes, TCHAR *buf, int *buflen)

No matter what the underlying text-format, this routine will always return UTF-16 data (for a Unicode build). The text is stored in the buf parameter, and the number of "characters" stored in buf is returned in the *buflen parameter.

?
TCHAR buf[200];
int buflen = 200;
// read a block of text as UTF-16 from the specified position
len_bytes   = textDoc->gettext(byte_offset, max_bytes, buf, &buflen);
// adjust offsets ready for next read
off_bytes  += len_bytes;
max_bytes  -= len_bytes;

Most importantly though, the number of bytes that were processed from the underlying file is returned from the function directly - i.e. the return value represents the number of ASCII/UTF-8/UTF-16 bytes that were processed during the conversion to UTF-16. This is required so that we can keep track of the "byte position" in the underlying file - to allow us to continue reading blocks of UTF-16 in an iterative fashion.

Even though the TextView will be reading UTF-16 data (and using UTF-16 based offsets for cursor positioning etc), we must access the underlying file using byte-offsets. This is to make the text-retrieval a direct form of access to the underlying file, converting whatever data happens to be at the byte-offsets into UTF-16. If we used UTF-16 coordinates to access the file content, we would have to convert this character-offset to a byte-offset by performing lengthy processing.

The new TextDocument::gettext function is a little more complicated than what we had before:

?
int TextDocument::gettext(ULONG offset, ULONG lenbytes, WCHAR *buf, int *buflen)
{
    BYTE *rawdata = buffer + headersize + offset;
    switch(fileformat)
    {
    case NCP_ASCII:
        return ascii_to_utf16(rawdata, lenbytes, buf, len);
    case NCP_UTF8:
        return utf8_to_utf16(rawdata, lenbytes, buf, len);
    case NCP_UTF16:
        return copy_utf16(rawdata, lenbytes/sizeof(WCHAR), buf, len);
    case NCP_UTF16BE:
        return swap_utf16(rawdata, lenbytes/sizeof(WCHAR), buf, len);
    default:
        *len = 0;
        return 0;
    }
}

We must use the TextDocument::fileformat member-variable to decide how to convert the underlying text into UTF-16. Notice that there is one conversion routine for each type of text that we will support.

One thing I should mention which isn't detailed here is the actual conversion process to UTF-16. We must be very careful that we never "split apart" UTF-16 surrogate pairs accidently when converting to UTF-16. This could potentially happen when converting from UTF-8 and we run out of buffer space to store both of the surrogate characters. The conversion routines all make sure that surrogate pairs are kept together.

Problems with MultiByteToWideChar

You may have noticed that I have written my own Unicode-conversion routines in the TextDocument::gettext function. I really wanted to use the MultiByteToWideChar API to perform all conversions to UTF-16. Unfortunately nothing is that simple. Although MultiByteToWideChar is good at converting valid UTF-8 data, it is not so good when it comes to invalid text-sequences (such as malformed or overlong sequences).

When it comes to processing this type of data the preferred behaviour for a text-editor is to indicate invalid sequences of UTF-8/UTF-16 by using a special Unicode character - "U+FFFD Unicode Replacement Character". The problem with MultiByteToWideChar is, it doesn't perform this conversion for invalid sequences - it just returns with a failure and you don't know how many characters were invalid. This makes it impossible to restart the conversion process, because you don't know where to restart.

By writing my own routines I was able to process both valid and invalid data in a manner that is more suitable for text-stream processing - i.e. more suitable for a text editor.

Line Buffer Management

Changing to Unicode and the "double coordinate system" means that the line-buffer scheme we developed earlier in the series needs revisiting. I'm not going to go into too much detail here because I know full well that I will be changing it yet again when we come to adding "gigabyte file support" later in the series. But this needs some discussion right now so here goes:

The line-buffer in Neatpad serves two purposes. Firstly, it provides a method to quickly locate a line of text's physical location within a file. This provides a kind of "random access" to the file content. The second purpose of the line buffer is to perform the reverse operation - i.e. given a physical "cursor offset", work out which line contains this offset.

Now that coordinate-system of the TextView is UTF-16, we need to rethink the design of the line-buffer. The problem is, we still need to know where lines of text are physically located within a file so we can't just change the line-buffer to UTF-16 coordinates. Of course a "single-format" editor such as Notepad would go down this route but because we must support multiple file-formats we need to be dealing with realphysical locations.

What I have done is add a second line-buffer to the TextDocument - adjacent to the original "byte-based" line-buffer. So the original line-buffer still holds real, physical byte-offsets of each line's starting position within the file. The new line-buffer records each line's starting position, however this time it stores the information as UTF-16 offsets (character positions) rather than byte-offsets. Even if the underlying file is UTF-8, this second line-buffer stores each line's offset as if it were encoded as UTF-16.

The diagram below should hopefully illustrate what I am trying to explain.

Moving to Unicode has introduced yet another problem: Exactly how do we initialize the line-buffer(s) with these "line start" offsets with all these extra formats? Previously the method used to search for CR/LF combinations was a simple byte search. Unfortunately this method will no longer be sufficient for our multi-format text editor:

We can't do a byte-search for "\r" and "\n" characters and expect it to work any more. There is no problem with ASCII and UTF-8 (they are still byte-based and CL/LF are no different), but UTF-16 presents a challange. The CR-LF ASCII sequence (0x0D followed by 0x0A) is actually "U+0D0A MALAYALAM LETTER UU" in Unicode. We must search specifically for U+0x000D and U+0x000A when processing a UTF-16 text file.

Unicode also defines it's own line-breaking and paragraph-breaking codepoints: U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR. However the convention in text-files is still to use CR/LF sequences so we must really support all these conventions.

We have two options for parsing lines of text and building the line-buffers. The first is to write separate routines - one for each format we will support. Although this might be the most efficient approach in terms of processing-speed, it is definitely not the most efficient in terms of code-maintanence. Perhaps when Neatpad is complete I will look at this approach, but for now I prefer the following method:

Quite simply, the better method for the time being is to use a generic line-parsing routine. The TextDocument::init_linebuffer remains intact, and still processes the file on a character-by-character basis, searching for CR/LF sequences. The difference is now, the file is converted into a stream of UTF-32 characters which enables us to handle all forms of text.

Text Iteration

You may be thinking that this is all getting quite complicated now - and you'd be right, it is! The main complication arises (as we already know) because the TextView deals in UTF-16 character offsets, whereas the TextDocument deals in byte-offsets. Although the TextView always retrieves UTF-16 text from the TextDocument, it must still do so using byte-offsets. This is not a terribly neat solution.

To solve the problem I have introduced a third C++ class called TextIterator. The purpose of this class is to provide a "bridge" between the coordinate system of the TextView and the underlying file-format that the TextDocument understands. This means that the TextView no longer asks the Document directly for text - all text retrieval now goes through the Iterator.

?
class TextIterator
{
    friend class TextDocument;
public:
    int gettext(WCHAR *buf, int len);
private:
    // only "friends" of the TextIterator can create them
    TextIterator(ULONG off, ULONG len, TextDocument *doc);
    // keep track of position within the specified TextDocument
    TextDocument *  text_doc;
    ULONG off_bytes;
    ULONG len_bytes;
};

As you can see the class definition for a TextIterator is very simple. It keeps track of the TextDocument that it is being used for, and the byte-offset within the document. These values are set when the TextIterator is constructed. The only code which actually does anything useful is shown below in the TextIterator::gettext function.

?
int TextIterator::gettext(WCHAR *buf, int buflen)
{
    // get text from the TextDocument at the specified byte-position
    int len = text_doc->gettext(off_bytes, len_bytes, buf, &buflen);
    // adjust the iterator's internal position
    off_bytes += len;
    len_bytes -= len;
    return buflen;
}

The TextIterator basically encapsulates the byte-based fileoffset details and hides them from the TextView. A single function has also been added to the TextDocument which is used to start a line-iteration:

?
TextIterator TextDocument::iterate_line(ULONG lineno, ULONG *linestart, ULONG *linelen)
{
    ULONG offset_bytes;
    ULONG length_bytes;
    lineinfo_from_lineno(lineno, linestart, linelen, &offset_bytes, &length_bytes);
    return TextIterator(offset_bytes, length_bytes, this);
};

The interate_line function returns an independent TextIterator object which can then be used to access the file's text in a transparent manner. An example of text-iteration using this new class is shown below:

?
TextIterator itor = m_pTextDoc->iterate_line(100);
WCHAR buf[200];
int len;
len = itor(buf, 200);

You can see how simple the process is now. The TextView now accesses the file-content through the TextIterator. Everything is line/character offset based as far as the TextView is concerned. The nasty byte-offsets and conversion details are hidden away in the Iterator and TextDocument, which is exactly how we want it.

In all probability I will end up changing the design yet again when some other issue crops up (I am expecting headaches with bidirectional text and complex scripts), but for the time being the TextView/TextIterator/TextDocument design that I have outlined here seems to work pretty well.

Additions to Neatpad

A quick mention on some changes to the actual Neatpad application. I have added three things. The first is command-line support. Now it is possible to specify a text-file at the commandline (just like with Notepad) and the file opens automatically when Neatpad starts.

The second addition is shell-menu support. There is a new setting in Neatpad's options to add an entry to Explorer's shell context menu for all filetypes, enabling you to right-click any file and select "Open with Neatpad". I always add this entry for Notepad when I build a new system and having the same (automatic) feature for Neatpad will be very useful in my opinion.

The last addition is window-position persistence. You may have noticed with Notepad that it saves it's window-position each time it exits, so that the next time it starts the window is restored to the saved position. I have gone one step further than this - Neatpad saves the window position for individual files rather than for the application as a whole. This means that you could open+close different files in Neatpad and they each remember their own position on screen.

The way I have done this is to use NTFS Alternate Data Streams. I have been dying to find a use for "NTFS Streams" since they first appeared in Windows NT and I believe I have found the perfect use for them. Each time a file is opened, an NTFS steam attached to the file (called Neatpad.WinPos) is opened as well. A WINDOWPLACEMENT structure is saved in this stream - so when a file is opened the SetWindowPlacement API is called using the saved structure. And when a file is closed, Neatpad's current window position is retrieved with GetWindowPlacement and saved back into the main file's Neatpad.WinPos stream.

Coming up in Part 10

The subject of Unicode has proven to be rather difficult to solve. In fact I went through several rewrites of the TextView/TextDocument classes before I arrived at the solution I've presented here. This is just one of the reasons why it took such a long time to get right - the other reason being that I had to do alot of background reading to make sure I understood all of the issues surrounding Unicode before I started.

I will mention again the book "Unicode Demystified" by Richard Gillam - this book is well worth a read and covers many more Unicode topics than I can present here. Although it was written for Unicode 3.0 don't let this put you off - the changes between Unicode 3.0 and 4.0 are fairly minimal and are basically just things like additions to the character repertoire.

Moving on to Part 10. The next tutorial will focus on the proper display of Unicode text. Understand that at the moment, all I have really done is turn Neatpad into a "wide-character" text viewer which happens to support UTF-8 and UTF-16 encoding formats. Although we are now using the "unicode" Windows APIs (specifically TextOutW) we are still a long way off being a real Unicode editor. Complex scripts, combining characters and bidirectional text are not supported yet. If you thought displaying Unicode text was a matter of simply calling TextOutW then think again - Unicode text display is a very complicated problem which cannot be solved using TextOut on it's own.

The next tutorial will therefore focus on the Uniscribe API. This API (available since Windows 2000) provides support for displaying complex-scripts and bidirectional text. We will have to redesign Neatpad's text-display engine slightly because of the way Uniscribe works, and also modify the mouse-input and selection routines, but hopefully after the next tutorial we will be in a very good position in terms of Unicode support.

‹ Introduction to Unicode up Transparent Text ›

5 Dec 2005

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Transparent Text | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Transparent Text

Design & Implementation of a Win32 Text Editor

It's been almost three months since I posted the last Neatpad tutorial, and it's also been over a year since I started this project. During this time I have been steadily working on the migration to the Uniscribe API. Alot of issues have become apparent with the way I am rendering text in Neatpad, so this tutorial will hopefully highlight these issues and offer a solution. There isn't going to be any code-download this time as I will be incorporating the ideas presented here into subsequent tutorials (which will be about Uniscribe).

Rendering text

So before we start looking at Uniscribe I want to take a moment to re-examine the process of text rendering. This is not going to be a very in-depth discussion - rather it will be an overview of text-rendering in general. As the basis for our discussion a single letter 'F' in italics will be used, as shown below.

The image shows a black glyph, representing the lower-case italic letter 'f', drawn on top of a grey rectangular background. The grey background represents the bounding rectangle of the character. Notice how the character overhangs the rectangle on the left and right edges. These measurements are called the left-bearing and right-bearing of a glyph.

In Windows the total width of a character is also represented as three values, called the ABC width. The A-width represents the width of the left-most overhanging portion of the glyph, whilst the C-width represents the right-most overhanging portion. The B-width measures the extent of the glyph. For characters such as the lower-case 'F' above, the A and C-widths are frequently negative values, which allows the characters to be positioned closer together when displayed as part of a string. Of course when a character has a negative A or C-width, it will overlap the space occupied by it's neighbouring characters. The purpose of this short tutorial is to highlight the issue with rendering glyphs that have negative A and C-widths.

All applications (including Neatpad) render text using one of the Windows text-display APIs - usually DrawText or TextOut. Text in Windows can be drawn in one of two ways - with and without a background rectangle. Text drawn with a background fill is referred to as opaque text, whilst text drawn without is called transparent text. This text-drawing feature is controlled with the SetBkMode API, or the ETO_OPAQUE option when calling ExtTextOut.

The problem of overhanging glyphs only presents itself when breaking a string into smaller segments and rendering them individually. The issue occurs because the act of filling the background causes the overhanging portions of a glyph to be overpainted, resulting in the characters being clipped by the neighbouring background rectangles. The image below illustrates the two methods of painting text. The text is made up of six individually rendered 'F's, each in a different colour.

Clipping (opaque text) | No clipping (transparent text)

The image on the left illustrates the text drawn using an opaque background-fill mode. This is the simplest way to draw text, because the text and background is rendered in one go. This is how Neatpad currently renders text. However problems occur when text is broken up into blocks. Of course the problem is made much worse by the Italic text - for most Roman text this issue never occurs.

?
SetBkMode(hdc, OPAQUE);
TextOut(hdc, x, y, szText, nTextLen);

The image on the right illustrates the ideal way to display multi-coloured text. In order to render the text this way the process had to be split in two phases. Firstly, the background was drawn in it's entirety (from left-to-right). Then the text was drawn 'transparently' over the top of the background.

?
FillRect(hdc, &backgroundRect);
SetBkMode(hdc, TRANSPARENT);
TextOut(hdc, x, y, szText, nTextLen);

Breaking the text with two phases will introduce flickering, so to remedy this side-effect we must resort to 'double-buffering' whenever we draw text. This means that we must draw each line of text into an off-screen buffer before displaying it visually. It is entirely necessary to draw text this way, and this is the direction I am heading in with Neatpad. However the consequence of moving to a double-buffering scheme means we can re-examine how selection-highlights are drawn.

Drawing Selection Higlighting

Selection-highlighting has always seemed to be a rather elusive subject - at least to me. The problem is, there aren't really any strict guidelines as to how one should represent text-selections, and it was only after delving into the world of Uniscribe and 'low level' glyph rendering, that I fully understood the issues involved.

The images below illustrate the two main methods of representing text-selection. For the purposes of this example I am using the same string of six 'F's. This time however two of the six glyphs are shown as 'selected' - using the default Windows system colours.

Background highlighting | Inversion highlighting

The two basic strategies can be referred to quite simply as Background highlighting, and Inversion highlighting.

  • Background highlighting is the simplest way to represent text-selections. In fact all that is required is to modify the text foreground and background colours to represent the selected range of text. Because the text has been drawn transparently on top of the background, you will notice how the glyphs overhang their character-boundaries. Wordpad and Internet Explorer use this method, as do many other text-editors such as Scintilla.
  • Inversion highlighting is more complex to achieve but produces better results (in my opinion). The effect is very similar to the old method of literally inverting the pixels within a rectanglar area. Of course to maintain the standard Windows colour-scheme the selected area is painted normally instead of truely inverting the colours. It also looks as if the selection highlight is on top of the text, rather than behind it. Notepad is a very good example that demonstrates this type of selection-highlighting.

My preference is for the 'inversion' method. When text is highlighted I like to think of the selection-rectangle being overlayed on top of the text, rather than being behind it. Personally I don't like the look and feel of the background-highlighting method. Because of the typical colour-scheme in Windows (white background, white selected text), some selected text seems to disappear when the selection highlight moves over them. If you look closely at the example you can see this effect clearly - because the two selected characters are white, any overlap onto a white background makes it look as if the characters have been truncated. I am moving to the 'inversion' method in Neatpad simply because it looks more professional.

The process of 'inversion highlighting' is rather more involved than you might expect. The problem we face is the issue of overhanging characters. In the example above the character is shown as selected - however the leading and trailing edges fall into unselected areas and must be painted in separate colours. When displaying this character in a selected state it must somehow be split apart - with the main character body rendered one way, and the left-and-right overhanging segments rendered diffierently. My solution to this problem is to use a three-phase rendering strategy, which is outlined below.

1a. Background | 1b. Mask selected areas

The first stage is to paint the background using a series of simple flat fills. Every character has it's regular bounding rectangle painted this way, with both normal and selected areas being rendered. An important detail to understand is the use of clipping at this early stage. Any selected-areas are masked immediately after being painted. What this means is that the ExcludeClipRect API is used to remove the selected areas from the current device-context's clipping-region. The reason why this detail is important will soon become clear.

2a. Normal text | 2b. Invert mask

Once the background has been painted in it's entirety (for all characters/glyphs), the second stage is to render the text over the top. A transparent drawing-mode is used to paint the text (SetBkMode with the TRANSPARENT setting). Because of the clipping-region created during the first pass, any selected (blue) areas are protected as the text is drawn over the them.

After the text been drawn the clipping-region for the device-context is inverted (ExtSelectClipRgn with RGN_XOR), which results in the selected areas becoming unmasked. The text we have just rendered will now be protected from subsequent drawing operations.

3a. Selected text | 3b. The result

The final stage is to redraw the text. The exact same text is drawn, directly over the top of the text we just drew. The difference is that the text is drawn in a single colour this time - using the system-highlight colour (i.e. white). Because of the clipping-region we have created, only the selected (blue) areas are modified this time. When the clipping-region removed the drawing is complete. The result is high quality text-output that would be suitable for a word-processing package.

The use of clipping is the key to making this method work. Although it seems as if the text is overdrawn, in reality it is not because of the careful use of clipping-regions. Importantly, when ClearType or another form of anti-aliasing is enabled, using clipping this way is absolutely necessary, because if we really did overdraw the text we would ruin the ClearType effect. Using clipping in the method outlined above protects us from this 'overdraw', even though we attempt to draw the text twice.

Of course there are always going to be issues with rendering text this way. Performing three separate passes will introduce a fair amount of flickering and basically dictates that we must use double-buffering when drawing all text. There is also the potential performance-decrease of drawing the text twice. Note however that this last issue can be managed quite easily by only performing this 'double drawing' when necessary - most of the time we can avoid drawing text twice (such as when there is no selection in the text, or if all the text is selected).

Although the method I have described here might seem quite strange if you've not encountered it before, I can promise you that it is not that unusual. The ScriptString API (and therefore apps like Notepad) use this exact same method, and you can be sure that any text-editing package that uses the 'inversion method' will use something very similar also. The point is, there really isn't any alternative to the method I have chosen. If your goal is high-quality text display then concessions must be made.

Coming up in Part 11!

It should be noted that I have taken an extreme example here. Most English text in a Roman typeface will not require complex rendering such as this - however because we must support all forms of text (especially the more exotic Unicode scripts) we must do things properly from this point on. In fact the inversion-method is entirely necessary for rendering with Uniscribe as we will find out shortly.

This tutorial is really just a preview of things to come. The technique of inversion-highlighting will be incorporated into Neatpad's text-display engine as we migrate to the Uniscribe API.

‹ Unicode Text Processing up Introduction to Uniscribe ›

25 Feb 2006

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Introduction to Uniscribe | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
scriptstring.zip

Published on 22 Jan 2012

16.32 Kb

Download

Introduction to Uniscribe

Design & Implementation of a Win32 Text Editor

Uniscribe is a low-level Win32 API that provides a high degree of control over the processing and display of Unicode text. The API is designed to provide a generic interface to all forms of Unicode text (complex or otherwise), and transparently handles properties such as bi-directional text and combining characters sequences.

Uniscribe is a single DLL called USP10.DLL, which contains all of the Uniscribe APIs. This DLL is present on Windows 2000 and above, or any computer with Internet Explorer 5.0 (or greater) installed. Two Platform SDK files (USP10.H and USP10.LIB) are provided by Microsoft to allow an application to make use of this complex-script support. An important point about Uniscribe is that it doesn't just handle complex-scripts - it can be used to process and display all Unicode text - so can be used as a direct replacement for existing text-output routines such as DrawText and TextOut.

The Uniscribe API is divided into two categories - the low-level API itself, and a wrapper library called ScriptString which hides much of the complexities of dealing with Uniscribe directly. The purpose of this tutorial is to give a brief introduction to the world of Uniscribe before we start delving in properly.

Uniscribe in Windows

When I first started Neatpad I was unfamiliar as to exactly what Uniscribe entailed, and it was only after researching Unicode that I fully appreciated the issues surrounding the display of Unicode text. Although Uniscribe occupies it's own section within the MSDN documentation (here), other than the occasional reference it is very easy to miss unless you already know of it's existence.

MSDN states that since Windows 2000, the ExtTextOut function (and others like TextOut, DrawText etc) have been extended to support complex scripts. Although this is true, it gives the impression that an application can call ExtTextOutW (the Unicode version) at any time with a buffer of UTF-16 text and it will always display correctly.

Unless Windows has been configured to do so, functions such as ExtTextOut do not automatically support complex scripts. The image above shows the "Regional and Language Options" dialog. The two settings which have been highlighted are not normally enabled by a default installation of American/English Windows.

Enabling complex-script support installs a number of extra libraries, after which ExtTextOut will use the Uniscribe when necessary to display complex scripts.

?
BOOL ExtTextOut (
  HDC hdc,          // handle to DC
  int X,            // x-coordinate of reference point
  int Y,            // y-coordinate of reference point
  UINT fuOptions,    // text-output options (ETO_GLYPH_INDEX etc)
  RECT    * lprc,         // optional dimensions
  LPCTSTR lpString,     // string
  UINT cbCount,      // number of characters in string
  INT * lpDx          // array of spacing values
);

ExtTextOut is most commonly used to display a string of text. However it can do much more than this. When the ETO_GLYPH_INDEX and ETO_PDY options are specified, ExtTextOut can be used to display a buffer of glyphs instead of characters. This feature of ExtTextOut is used when displaying a string containing complex-scripts, as the diagram below illustrates.

Text drawing in Windows 2000 and above

For any complex string containing complex scripts, ExtTextOut makes use of Uniscribe to display it. Uniscribe breaks the string down into groups of glyphs and then re-calls ExtTextOut, this time with the ETO_GLYPH_INDEX option, and a buffer of glyph-indices instead of the original character values. For regular Unicode text which doesn't require any special processing, ExtTextOut behaves exactly the same as it did under previous Windows versions.

You may be wondering why Uniscribe is necessary if routines such as DrawText and TextOut can for the most part render complex scripts quite sucessfully. For applications which just output single strings of text, Uniscribe is not necessary.

It is only when a string must be broken up (for the purposes of styling/formatting) that Uniscribe is required. It is just not possible to split a Unicode string into sections (as we have been with Neatpad up 'til now). Doing so breaks all kinds of things such as contextual shaping behaviours and bidirectional support. A modern text-editor simply must support Unicode and all the various scripts that come along with that - we have no other choice than to move to Uniscribe.

The ScriptString API

The ScriptString API is designed for applications which want to display text in a single font and colour. Notepad (and the standard Windows EDIT control) is a prime example of the ScriptString API. One of the nice features of this API is that it allows you to display a string of text, with a portion of that string optionally displayed as 'selected'. This is actually a very nice touch as it saves a tremendous amount of effort.

The ScriptStringAnalyze function is the starting point with Uniscribe. It is a pretty intimidating function to look at. However its purpose is used to perform shaping and glyph-generation for any string of Unicode text, and returns a SCRIPT_STRING_ANALYSIS structure when complete.

?
HRESULT WINAPI ScriptStringAnalyse (
  HDC hdc,
  void * pString,
  int cString,
  int cGlyphs,
  int iCharset,
  DWORD dwFlags,
  int iReqWidth,
  SCRIPT_CONTROL          * psControl,
  SCRIPT_STATE            * psState,
  int * piDx,
  SCRIPT_TABDEF           * pTabdef,
  BYTE * pbInClass,
  SCRIPT_STRING_ANALYSIS  * pssa
);

SCRIPT_STRING_ANALYSIS is an opaque structure - there is no documention which details what it contains. This is not important though as this structure is simply passed to the rest of the ScriptString API without requiring any further knowledge.

?
HRESULT WINAPI ScriptStringOut (
  SCRIPT_STRING_ANALYSIS    ssa,
  int iX,
  int iY,
  UINT uOptions,
  RECT                    * prc,
  int iMinSel,
  int iMaxSel,
  BOOL fDisabled
);

ScriptStringOut is used to display a string of text that was previously analyzed. Note that a text-string is not specified with this call - only the SCRIPT_STRING_ANALYSIS structure is passed which contains all the necessary information to display the original string.

?
HRESULT WINAPI ScriptStringXtoCP (
  SCRIPT_STRING_ANALYSIS    ssa,
  int iX,
  int * piCh,
  int * piTrailing
);

ScriptStringXtoCP is an interesting function. It provides a mechanism for caret and mouse positioning within a string of Unicode text.

?
HRESULT WINAPI ScriptStringCPtoX (
  SCRIPT_STRING_ANALYSIS    ssa,
  int icp,
  BOOL fTrailing,
  int * pX
);

ScriptStringCPtoX is the counterpart to ScriptXtoCP. It performs the opposite task - converting a string-position to a display-coordinate.

?
HRESULT WINAPI ScriptStringFree(
  SCRIPT_STRING_ANALYSIS  * pssa
);

When an application has finished displaying the string the ScriptStringFree function can be used to clean up. There are more ScriptString functions than what I have listed here, but with just these five an application can implement the front-end to a fully-functional text-editor with minimal effort.

The image above shows a simple application I wrote which demonstrates the ScriptString API. The source-code and demo executable can be downloaded at the top of this article.

An oddity of ScriptString is this: ScriptStringOut fails if the device-context used to render is not the same as the one used when analyzing the string with ScriptAnalyze!

Introducing UspLib

The main problem with the ScriptString API is its inability to display text in more than one font and colour. This makes it particularly unsuitable for our purposes with Neatpad. Our only option is to make use of the low-level Uniscribe functions directly.

USPLib is a library I have written to provide a far richer capability than ScriptString can offer. This new library provides a wrapper around the low-level Uniscribe API that we will be discussing over the next couple of tutorials. UspLib is very similar in approach to the ScriptString Uniscribe wrapper, but goes alot further in terms of text-colouring and formatting.

?
USPDATA * USP_Allocate();

The first API is USP_Allocate. This function returns a pointer to a USPDATA object which must be used for subsequent UspLib operations.

?
BOOL USP_Analyze (
  USPDATA   * uspData,
  HDC hdc,
  WCHAR * wstr,
  int wlen,
  ATTR      * attrRunList,
  UINT flags,
  USPFONT   * uspFont
);

USP_Analyze is similar to ScriptStringAnalyze. The difference is, a string of text can be re-analyzed using an existing USPDATA object.

?
void USP_ApplyAttributes (
  USPDATA  * uspData,
  ATTR     * attrRunList
);

Once a string has been analyzed (i.e. itemized and shaped etc.), colour-attributes can be reapplied at any time using the USP_ApplyAttributes. The font-information stored in the ATTR run-list is ignored.

?
void USP_ApplySelection (
  USPDATA  * uspData,
  int selStart,
  int selEnd
);

USP_ApplySelection performs a similar task to USP_ApplyAttributes. However this time only the selection-flags are modified in the USPDATA object.

?
int USP_TextOut (
  USPDATA  *  uspData,
  HDC hdc,
  int xpos,
  int ypos,
  RECT     *  rect);

USP_TextOut is the counterpart to ScriptStringOut. It takes as input the USPDATA object which was previously analyzed, and draws it to the specified location. Any fonts, colours and selection-highlights are applied to the text as it is drawn.

?
void USP_Free(USPDATA * uspData);

USP_Free should be called then the USPDATA object is no longer needed. Over the course of the next two or three tutorials I will be detailing how I have implemented UspLib, and will provide details and examples of using Uniscribe directly.

I have designed UspLib in isolation from Neatpad. My intention is that it is a completely stand-along library, which can be used by any application to add complex-script support. It should certainly be possible to import UspLib into your projects and use it straight away, because it contains no dependencies other than the Uniscribe DLL.

Further Reading

There is very little information available about Uniscribe other than what is available in MSDN.

Uniscribe Platform SDK Reference

Supporting Multilanguage Text Layout and Complex Scripts with Windows NT 5.0

Globalization Step-by-Step - Complex Scripts Awareness

Windows Glyph Processing

There is also the CSSamp example program from Platform SDK, in the Samples sub-directory:\PlatformSDK\Samples\winui\globaldev\CSSamp

Alternatives to Uniscribe

Not every editor uses Uniscribe. If open-source is your thing then there are currently two very impressive efforts available which offer a very strong alternative to Uniscribe. There is also an equivalent version of Uniscribe available for Apple's OSX called ATSUI.

International Components for Unicode (ICU) is IBM's open-source Unicode support library. It contains alot of functionality, from character-conversions, analysis, searching and layout.

Pango is an open-source library for laying out and rendering Unicode text. It appears to sit on top of the GTK display library and can specify either Cairo or Win32 (Uniscribe) rendering back-ends. It offers a more complete solution than Uniscribe and appears to be very well designed and implemented. However Pango is UTF-8 based so this may be a consideration if the rest of your application is UTF-16.

Apple Type Services For Unicode Imaging (ATSUI) is Apple's own version of Uniscribe, although it appears to be higher-level than Microsoft's effort. A brief look at the documentation for ATSUI indicated a much easier-to-use design, and substantially better documentation than Microsoft had managed for Uniscribe.

Coming up in Part 12

This was just a short introduction to Uniscribe - hopefully you are a little more aware of what Uniscribe is capable of, and have downloaded and tested the ScriptString sample program.

Part 12 will focus on the first two Uniscribe functions: ScriptItemize and ScriptLayout. There is alot of detail to cover with just these two APIs and it won't be until Part 13 that we actually see any text being drawn this way with Neatpad.

Lastly, I've not had much feedback in the last few months about Neatpad - did you read this tutorial and find it useful?

‹ Transparent Text up Uniscribe Mysteries ›

27 Feb 2006

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Uniscribe Mysteries | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Uniscribe Mysteries

Design & Implementation of a Win32 Text Editor

The last tutorial presented a very brief overview of the Uniscribe ScriptString API. Unfortunately ScriptString is insufficient for our purposes with Neatpad because of the limitations of a single font and colour. The aim of this tutorial is to therefore investigate the "low-level" Uniscribe API. Because we have very specific requirements for Neatpad's text display our approach that of a multi-font, syntax-coloured text editor.

The string of Unicode text shown below will be used as the basis for much of our discussion. The Arabic phrase in the middle has been chosen because it's Unicode properties suit the context of our discussion, not because they have any special meaning.

HelloيُساوِيWorld

You will notice that two of the "glyphs" in the Arabic phrase above have been highlighted in different colours to the rest of the string. These two letters are "U+0633 ARABIC LETTER SEEN" and "U+0627 ARABIC LETTER ALEF". In isolation they display as follows:

سا
س‌ا

The box on the left shows the two characters rendered with contextual-shaping (assuming you are using a Unicode-enabled web-browser such as Internet Explorer and have the appropriate fonts installed). This is the behaviour we are aiming for. The box on the right shows the characters rendered separately from each other. If both boxes look the same then your browser is not displaying Unicode properly.

One of the big reasons Unscribe exists is to provide the kind of complex "shaping" behaviour illustrated above. The requirement on our part (as programmer) is that we do not split Unicode strings into individual characters because this would break the shaping behaviour we are aiming for. Therefore the major goal of this tutorial is to explain how characters can be drawn individually (in different colours) whilst still maintaining the contextual shaping.

Basic Outline

The basic set of steps for drawing text with Uniscribe are outlined below. Note that I am omitting word-wrapping (and the ScriptBreak API) for the moment. So assuming that we have a string of UTF-16 Unicode text, this is what we do:

  1. ScriptItemize - to break the string into distinct scripts or "item-runs".
  2. Merge item runs with application-defined "style" runs to produce finer-grained items.
  3. ScriptLayout - to potentially reorder the items.

Then for each item/run (in the order dictated by the ScriptLayout results)

  4. ScriptShape - to apply contextual shaping behaviour and convert the characters from each run into a series of glyphs.
  5. ScriptPlace - to calculate the width and positions of each glyph in the run.
  6. Apply colouring/highlighting to the individual glyphs.
  7. ScriptTextOut - to display the glyphs.

This outline closely follows how Microsoft recommends you use the Uniscribe API. Note however that I have included an extra step#6 (text-colouring) which is not mentioned in MSDN. The reasoning behind this difference will be explained as we progress through the tutorial. I will leave the subject of word-wrapping to a later tutorial, as this is more of a problem of line-buffer management rather than using Uniscribe.

?
BOOL UspAnalyze (
  USPDATA         * uspData,
  HDC hdc,
  WCHAR * wstr,
  int wlen,
  ATTR            * attrRunList,
  UINT flags,
  SCRIPT_TABDEF   * tabDef,
  USPFONT         * uspFont
);

The function prototype above is for a function called UspAnalyze. It is part of the new UspLib text-rendering engine that I have written for Neatpad. UspAnalyze is similar in many ways to ScriptStringAnalyze, but with the additional capability of allowing the caller to specify font and style information for the string.

The rest of this tutorial will begin to focus on each aspect of the Uniscribe API as outlined above and will discuss any issues related to each stage. However each stage that we look at will be a key step towards implementing the UspAnalyze function.

1. ScriptItemize

ScriptItemize is usually the first Uniscribe function to be called when displaying a string of Unicode text. It's purpose is to identify the various scripts in a string, and then split this string into items (or runs) according to the script, with one item per script.

H | e | l | l | o | ي | ُ | س | ا | و | ِ | ي | W | o | r | l | d
0048 | 0065 | 006C | 006C | 006F | 064A | 064F | 0633 | 0627 | 0648 | 0650 | 064A | 0057 | 006F | 0072 | 006C | 0064
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16

The table above illustrates how the UTF-16 string "HelloيُساوِيWorld" would be treated by ScriptItemize. The characters are shown in logical order - in other words, the order that they appear when stored in memory. The string has been divided into three segments. Note that these items are derived purely by their script - not by the finer-grained glyphs and grapheme clusters that are present in the string.

?
HRESULT WINAPI ScriptItemize(
  WCHAR * wszText,       // pointer to unicode string
  int wszLength,     // count of WCHARs
  int cMaxItems,     // length of pItems buffer
  SCRIPT_CONTROL * psControl,
  SCRIPT_STATE   * psState,
  SCRIPT_ITEM    * pItems,        // out - array of SCRIPT_ITEM structures
  int * pcItems        // out - count of items
);

ScriptItemize returns an array of SCRIPT_ITEM structures, one for each "shapable" item (script) in the paragraph of text. The number of structures is returned in *pcItems. In the example above, *pcItems would hold the value "3". This SCRIPT_ITEM structure is very simple and is shown below.

?
struct SCRIPT_ITEM
{
   int iCharPos;
   SCRIPT_ANALYSIS  a;
};

The SCRIPT_ITEM::iCharPos variable is used to identify the starting position of each "run" of text in the string. The SCRIPT_ANALYSIS child structure holds alot of extra information about the run including the reading-direction and the shaping-engine that should be used to convert the run into glyphs.

The image below this time illustrates how our Unicode string is represented by an array of SCRIPT_ITEM structures:

Notice that there is always a "hidden" SCRIPT_ITEM on the end of the array which represents the end-of-string. This makes it possible to calculate the length of each SCRIPT_ITEM by using the following construct:

?
itemLength = pItems[i+1].iCharPos - pItems[i].iCharPos;

There are a couple of general points worth making here. Notice that the first parameter to ScriptItemize is a WCHAR *. There is no ANSI version of this function so from now on Neatpad will be a pure Unicode application. Unless we can use the Microsoft Layer for Unicode (MSLU) we will have to drop support for Win9x.

Note also that you can never know in advance how many SCRIPT_ITEM s will be returned for a string of text, so it is usually necessary to use a loop of some kind - which allocates more and more memory for the SCRIPT_ITEM buffer until the call to ScriptItemize succeeds:

?
SCRIPT_CONTROL scriptControl = { 0 };
SCRIPT_STATE   scriptState   = { 0 };
SCRIPT_ITEM   *itemList      = 0;
int itemCount;
do {
    itemList = realloc(itemList, ... );
    hr = ScriptItemize(
            wstr,
            wlen,
            allocLen,
            &scriptControl,
            &scriptState,
            itemList,
            &itemCount);
    if(hr != S_OK && hr != E_OUTOFMEMORY)
        break;
} while(hr != S_OK);

A word of warning here - make sure you always pass fully initialized SCRIPT_CONTROL and SCRIPT_STATE structures to ScriptItemize even if their contents are initialized to all "zeros". Unless both these structures are specified, the Unicode bi-directional algorithm will not be used for the purposes of itemizing the string. This can result in incorrect identification of item-run positions in some circumstances (such as LTR and RTL scripts appearing in the same string).

Interestingly, MSDN says that when the SCRIPT_CONTROL and SCRIPT_STATE are NULL the itemization is based purely on character code. When non-null, the full bidirectional algorithm is applied as stated above. For this latter case the entire paragraph must be in memory. Although I'm not going to go down this path, this does suggest a method for handling arbitrarily long lines of text that cannot reside in memory as whole paragraphs.

2. Merging Style Runs

The reason we are using Uniscribe directly instead of the ScriptString functions is because we want finer-grained control over text colouring and font selection. And we have now reached the point (after calling ScriptItemize) where Microsoft's documentation advises us to merge "application-defined" style runs with the item information returned by ScriptItemize. Here's the quote from MSDN:

"Before using Uniscribe, an application divides the paragraph into runs, that is, a string of characters with the same style. The style depends on what the application has implemented, but typically includes such attributes as font, size, and color....Merge the item information with the run information to produce runs with a single style, script and direction."

This quote is one of the most confusing, cryptic and misleading statements in the whole of the Uniscribe documentation. The problem is, there are no hints in MSDN as to how one should merge style-runs with item-runs, or even what a "style run" actually is. We will look at how to "merge runs" a little further down, but first let's understand what is meant by the term "style run".

Of course, a style-run is whatever an application wants it to be. In essence it is a range of text that has been assigned a specific set of attributes. In the case of Neatpad I have used an ATTR structure to represent colour and font - one for each character in a string of text. The string of text and the attribute-list looked something like this:

?
WCHAR buff[ MAXLINELEN ];
ATTR  attr[ MAXLINELEN ];

However since migrating to Uniscribe and the 'inversion-highlighting' scheme, I have extended the ATTR structure somewhat, so that it is no longer a 'one ATTR per character':

?
struct ATTR
{
   COLORREF fg;    // foreground text colour
   COLORREF bg;    // background text colour
   int len   : 16;    // length of this run (in WCHARs)
   int font  : 7;     // font-index
   int sel   : 1;     // selection flag (yes/no)
   int ctrl  : 1;     // show as an isolated control-character
};

The foreground and background colours remain unchanged. The new structure-members are detailed below.

  • The first change is a new length field which represents the length (in characters) of an attribute-run - simply because Uniscribe likes to deal with "runs" of things rather than single characters. I won't be modifying the existing Neatpad code just yet (which assumes 1 ATTR per character) and this extra field will just be used for "internal housekeeping" when dealing with the Uniscribe API.
  • The font field is no different than before. It is still used as an index into a font-table.
  • The sel boolean is used to indicate the selection-state of the text-run - in other words, if the run should be rendered with selection-highlighting. This is quite an important change - I no longer store the selection-colours in ::fg and ::bg - a separate flag is used to indicate whether a character (or range of text) is selected. The move to an 'inversion highlighting' scheme requires this change.
  • The ctrl boolean is the last addition and is used to indicate whether or not the characters in the text-run should be rendered normally, or as individual control-characters.

The problem we now face is that we have two lists of unrelated entities - a SCRIPT_ITEM list which identifies the position of scripts within the original character array, and an ATTR list which identifies the ranges of style in the original string. We need to understand what MSDN means when it instructs us to merge these two unrelated lists together:

?
SCRIPT_ITEM *itemList;
ATTR        *attrList;

The basic process is to look at the style-runs and item-runs together and identify any position within the string where a run of one type overlaps another. For example, suppose a SCRIPT_ITEM run overlaps the boundary-position between two ATTR structures. This SCRIPT_ITEM would have to be split into two new halves - each representing a different ATTR style-run.

The way the split occurs is like this: the SCRIPT_ITEM::iCharOffset variables are modified to point to new positions within the original string and an array of ITEM_RUN structures is built up which holds these new character-postions. The other contents of the SCRIPT_ITEM (i.e. the SCRIPT_ANALYSIS structure) must be duplicated between the resulting two halves. Think of it as follows: The ScriptItemize function first breaks the string into discrete units based on script. The merge process then further breaks the string into smaller units based on style, should there be any overlap between the two.

The following diagram hopefully illustrates what is meant by a "style merge":

Now here's the problem. If we break up a SCRIPT_ITEM won't this affect the contextual shaping behaviour of the Uniscribe engines? The short answer is, yes, we will break the Uniscribe shaping behaviours by breaking up a string - and no, there is no magic way to get around this problem.

You may notice in the above diagram that I have written "font only" next to the ATTR style runs. This is delibrate, because although Microsoft advises us to break up a string based on style, this is not really correct. In fact, breaking a string due to colour differences (for the purpose of selection/syntax highlights) at this stage is wrong:

We must only take fonts into account when merging style-runs and item-runs, and ignore colour-information entirely.

Hopefully I have gotten this point across adequately. After following the advise of the Microsoft docs I wasted about a week trying to figure out how to colourise a string only to realise that I was going about it the wrong way. Syntax-colouring (or any kind of text colouring for that matter) must be applied to a string after the shaping has taken place - i.e. after ScriptShape and ScriptPlace have been called, and just prior to calling ScriptTextOut. This doesn't mean that we can't store colours in our ATTR structures - it's just that we don't use this information whilst performing the 'merge'. Any ATTR structures which share the same font must therefore be coalesced into a single run by the merge process before doing any "splits".

OK so once we have broken up the ATTR and SCRIPT_ITEM structures what do we do with them? I have defined a new structure called ITEM_RUN which contains the necessary content from the SCRIPT_ITEM and ATTR structures:

?
struct ITEM_RUN
{
   SCRIPT_ANALYSIS  analysis;      // from the original SCRIPT_ITEM
   int charPos;       // character-offset within the original string
   int len;           // length of run in WCHARs
   int font;          // only font is required, not colours
   ...
};

ITEM_RUN basically allows us to keep "formatting" information alongside the item-runs. Once we have itemized the string, Uniscribe only cares about the SCRIPT_ANALYSIS structures for each run. The other members of the ITEM_RUN structure are for our own private use. The item-run-list is stored inside the USPDATA structure for the string, in the itemRunList field:

?
struct USPDATA
{
   ITEM_RUN   * itemRunList;
   int itemRunCount;
   ...
};

The algorithm to merge runs is actually quite complicated - in fact it's one of the trickier aspects with Uniscribe programming, not helped by the fact that Microsoft give absolutely no hint as to how this should be performed, other than the 7-year old CSSamp application from the 1998 article "Supporting Multilanguage Text Layout and Complex Scripts with Windows NT 5.0".

To solve this problem I have written a new function called BuildMergedItemRunList that builds an array of ITEM_RUN structures for a given Uniscribe string. It performs two tasks - calling ScriptItemize and then merging the results with the style-runs specified by attrList.

?
BOOL BuildMergedItemRunList(
                 USPDATA  * uspData,       // in/out - holds results of merge
                 WCHAR * wstr,
                 int wlen,
                 ATTR     * attrList,
 );

BuildMergedItemRunList is an private function to USPLib, and is called by UspAnalyze as one of the first steps when building a USPDATA object. Taken in isolation, the function is used something like this:

?
ATTR attrList[2] =
{
    { RGB(0xff, 0x00, 0xff), RGB(0,0,0), 5, 0, 0 },    // five characters using font#0
    { RGB(0xAA, 0x22, 0xAA), RGB(0,0,0), 6, 1, 0 }     // six  characters using font#1
}
BuildMergedItemRunList(uspData, L"Hello World", 11, attrList);

Understand that the big advantage of using Uniscribe is the contextual-shaping and complex-script support. Dividing a Unicode string into sections by splitting SCRIPT_ITEM structures will break the script-shaping behaviour that we seek. We must try to keep the number of split SCRIPT_ITEM s to a minimum - and splitting based on colour differences at this stage is wrong. Although Neatpad will already have built it's ATTR style-lists before displaying text with Uniscribe, using the colour information in these lists must occur after shaping has taken place.

Finally, if you are building a text-editor that only deals with a single font then you can completely skip this phase and save yourself alot of work (or even use the ScriptString API if you don't want syntax colouring!)

3. ScriptLayout

The next stage with Uniscribe is to take the merged item-runs and establish the correct visual order for display. In our case we use the array of ITEM_RUN structures produced by BuildMergedItemRunList. This is an important step and is the key to the correct display of bidirectional text. Note that unless a string contains right-to-left scripts reordering is not necessary but we still need to go through the motions because we won't know until runtime what kind of scripts and languages we might be processing.

The Uniscribe ScriptLayout function is called to perform the reordering, and uses the Unicode Bidirectional Algorithm to achieve this task.

?
HRESULT WINAPI ScriptLayout(
   int cRuns,
   BYTE * pbLevel,              // in
   int * piVisualToLogical,    // out
   int * piLogicalToVisual     // out
);

ScriptLayout takes as input a simple array of BYTE s which represent the bidi run-embedding levels of the string - one BYTE per item-run. This bidi run-embedding value is stored in the SCRIPT_STATE::uBidiLevel variable for each ITEM_RUN. It is up to us to build this BYTE[] array before calling ScriptLayout.

We have to therefore manually extract the uBidiLevel variable from each item-run. uBidiLevel is buried deep within each SCRIPT_ANALYSIS, as a member of the SCRIPT_STATE structure. Once the BYTE[] array is built the ScriptLayout API can be called. It all seems like rather alot of work just to return a further array of integers but thats just the way it is. Presumably the Uniscribe developers did it this way because they assumed that you would be creating and merging your own ITEM_RUN (or similar) structures.

?
VOID BuildVisualMapping( ITEM_RUN *  itemRunList,
                         int itemRunCount,
                         int visualToLogicalList[]  // out
  )
{
    int i;
    BYTE * bidiLevel = malloc(itemRunCount * sizeof(BYTE));
    // Manually extract bidi-embedding-levels ready for ScriptLayout
    for(i = 0; i < itemRunCount; i++)
        bidiLevel[i] = itemRunList[i].analysis.s.uBidiLevel;
    // Build a visual-to-logical mapping order
    ScriptLayout(itemRunCount, bidiLevel, visualToLogicalList, NULL);
    // free the temporary BYTE[] buffer
    free(bidiLevel);
}

The function above shows how obtain the visual-mapping list given an array of ITEM_RUN structures. This list is essential when displaying a string of text, or in fact doing anything which requires visual-order processing such as mouse/caret hit testing.

?
int xpos = 0, ypos = 0;
for(visualIdx = 0; visualIdx < itemRunCount; visualIdx++)
{
    int logicalIdx    = visualToLogicalList[visualIdx];
    ITEM_RUN *itemRun = itemRunList[logicalIdx];
    ProcessRun(itemRun, xpos, ypos);
    xpos += itemRun->width;
}

This type of processing-loop is necessary because even though we may be dealing with right-to-left scripts (i.e. Arabic or Hebrew), when it comes to text-display we still draw everything from left-to-right, including the 'backwards' runs. The visual-to-logical list provides a way to map from a visual to logical index and ensures we always process the runs in the appropriate order.

Coming up in Part 13

Uniscribe is a very complicated business as you can probably tell from reading this tutorial. Unfortunately this is a necessary evil, as all software written today should be fully Unicode compliant. Don't think for a minute that Uniscribe can be ignored - we need to support Unicode, and we must accept the added complications that it brings. The days of ASCII/English text display have gone for good.

So far we have covered the process of breaking up, and reordering a string of Unicode text into a series of item-runs. However we are still only half-way towards implementing the UspAnalyze function. The next tutorial will reveal how to take the item-runs we have produced and generate glyph and width information using the ScriptShape and ScriptPlace APIs.

‹ Introduction to Uniscribe up More Uniscribe Mysteries ›

1 Mar 2006

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


More Uniscribe Mysteries | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

More Uniscribe Mysteries

Design & Implementation of a Win32 Text Editor

Uniscribe Mysteries continued...

We are going to pick up directly from where we left the last tutorial, in which we began to look at the Uniscribe API in detail. Remember that we are still working on the UspAnalyze function, and the sequence of events last time had led us to the point where we had broken a string of Unicode text into several item-runs. Below are the steps we made to get this far:

  1. ScriptItemize - to break the string into distinct scripts or "item-runs".
  2. Merge item runs with application-defined "style" runs to produce finer-grained items.
  3. ScriptLayout - to potentially reorder the items.

The result of this work was an array of ITEM_RUN structures (called itemRunList) and the visual-logical mapping array (called visualToLogicalList) - which tells us in what order to display the runs. Both these arrays are stored inside the USPDATA object:

?
struct USPDATA
{
    ...
    ITEM_RUN  * itemRunList;
    int itemRunCount;
    int * visualToLogicalList;
    ...
};

The next task is to take each item-run in turn and get to the point where we can actually render some text (using ScriptTextOut). This will involve calling two more closely related Uniscribe functions (ScriptShape and ScriptPlace) for each run. Below are the steps we will now follow:

  4. ScriptShape - to apply contextual shaping behaviour and convert the characters from each run into a series of glyphs.
  5. ScriptPlace - to calculate the width and positions of each glyph in the run.
  6. Apply colouring/highlighting to the individual glyphs.
  7. ScriptTextOut - to display the glyphs

4. ScriptShape

Of all the Uniscribe functions, ScriptShape is probably the most important. It's purpose is to convert a run of Unicode characters into a series of glyphs ready for display. ScriptShape supersedes the functionality provided by the GetCharacterPlacement API but is quite similar in the type of data it returns.

ScriptShape is a fairly complicated function. It takes as input a single run of text (as identified by the SCRIPT_ITEM / ITEM_RUN structures), and also the SCRIPT_ANALYSIS structure associated with each item-run.

?
HRESULT WINAPI ScriptShape(
   HDC hdc,
   SCRIPT_CACHE     * psc,
   const WCHAR * pwsChars,         // in
   int cChars,
   int cMaxGlyphs,
   SCRIPT_ANALYSIS  * analysis,         // in
   WORD * pwOutGlyphs,      // out - array of glyphs
   WORD * pwLogClust,       // out - glyph cluster positions
   SCRIPT_VISATTR   * psva,             // out - visual attributes
   int * pcGlyphs          // out - count of glyphs
);

Calling this function results in a bewildering array of information. Let's look at each parameter in turn to understand what they represent.

  • psc is a pointer to a SCRIPT_CACHE object. This object must be intialized to NULL before ScriptShape is called for the first time.
  • pwsChars and cChars together identify the range of Unicode text (from the original character string) that makes up the current run.
  • analysis is a pointer to the SCRIPT_ANALYSIS structure for each run.
  • pwOutGlyphs[] is a buffer of WORD values, which receives the "glyph-indices" that make up the run. A glyph-index is a value unique to a particular font - it is the value which identifies a particular glyph image in that font. The size of the pwOutGlyphs buffer must be specified with the cMaxGlyphs parameter. When ScriptShape returns, the number of items stored in pwOutGlyphs is returned in *pcGlyphs.
  • psva[] points to a buffer of SCRIPT_VISATTR structures. This array runs parallel to the glyph-list (pwOutGlyphs), so must be allocated to the same size. Other than being a required input to ScriptPlace, I haven't found any use for the SCRIPT_VISATTR information so far.
  • pwLogClust[] is an array of WORD values. There is one WORD for every character (16bit WCHAR) in the run of text, so each element of pwLogClust corresponds exactly to a character position within the original text. This also means that the size of the pwLogClust buffer must be the same length as the run of text - cChars units long to be exact.

The most important parameter here is the pwLogClust[] array, the contents of which can be used to map between logical character positions and glyph-cluster positions. We will be looking at this array in more detail in the next tutorial.

Font Fallback

The majority of fonts do not support the full range of characters as defined by Unicode. In fact I don't know of any font which can display all Unicode scripts and languages. One of the nearest is "Arial Unicode MS" - which is available on the Microsoft Office CDs - but even this font only has around 55,000 characters available. Missing glyphs in a font usually (but not always) results in those little square boxes being displayed.

Applications usually solve this problem by utilizing specific fonts for each Unicode script type. This process is referred to as Font Fallback, and is implemented when the primary display font (say, for a text-editor) does not contain the appropriate glyphs to render all characters in a string. An internal lookup-table is searched for a 'backup font', from which the required glyphs can be substituted in favour of the missing glyphs in the primary font.

Font-fallback is not handled by the low-level Uniscribe API - only the ScriptString API has this facility. All Uniscribe-based applications are therefore required to have a built-in list of fallback fonts. For this reason I have decided not to implement Font-fallback in UspLib. It will be Neatpad's responsibility to handle font-fallback, and substitute fonts can be specified in the ATTR style-runs when analysing each line of text.

5. ScriptPlace

ScriptPlace takes the output of ScriptShape (the glyph-index-list and SCRIPT_VISATTR list) and generates glyph advance-width information. Advance-widths are simply the offset in pixels from one glyph to the next. This information is returned in an array of integers (piAdvance), which can be used to position the output coordinates when displaying text and also for mouse hit-testing.

?
HRESULT WINAPI ScriptPlace(
   HDC hdc,
   SCRIPT_CACHE     * psc,
   WORD * pwGlyphs,       // in - the results from ScriptShape
   int cGlyphs,        // in - number of glyphs in pwGlyphs
   SCRIPT_VISATTR   * psva,           // in - from ScriptShape
   SCRIPT_ANALYSIS  * analysis,       // in - from the ITEM_RUN
   int * piAdvance,      // out - array of advance widths
   GOFFSET          * pGoffset,       // out - array of GOFFSETs
   ABC              * pABC            // out - pointer to a single ABC structure
);

Instead of accepting a buffer of WCHAR characters as input (as did ScriptShape), ScriptPlace requires the buffer of glyph-indices that were produced by ScriptShape. The parameters of note are:

  • pwGlyphs[] (and the corresponding cGlyphs) is the same array of glyphs as returned by ScriptShape.
  • psva [] is the SCRIPT_VISATTR array returned by ScriptShape.
  • piAdvance[] points to a buffer of integers, which will receive the list of advance-widths for the run. There is one entry in piAdvance for each glyph in pwGlyphs. The piAdvance array must therefore be allocated to the same size as pwGlyphs.
  • pGoffset[] points to a buffer of GOFFSET structures. These structures identify the offset of each glyph as it should be displayed. MSDN confusingly documents this parameter as a single GOFFSET structure - however pGoffset must also be allocated to the same length as the pwGlyphs array.

Finally, the width of the item-run is represented by the ABC structure pointed to by the pABC parameter. The total width of each run can be calculated using the following expression:

?
runWidth = abc.abcA + abc.abcB + abc.abcC;

Note that the same value can also be calculated by summing together all of the integers in the piAdvance array.

?
for(i = 0; i < uspData->itemRunCount; i++)
    ShapeAndPlaceItemRun(hdc, &uspData->itemRunList[i]);

ScriptPlace is so dependent on the results of ScriptShape that the two functions are usually called together and isolated in a wrapper function. The ShapeAndPlaceItemRun function is used to this effect, and is called once for each item-run in the string.

Tab Expansion

Handling tabs is really easy with Uniscribe, even though there is no built-in support. The thing to understand is, any character in the original text-string will always be represented by at least one glyph after ScriptShape is called. This is even true for non-displayable control-characters such as carriage-returns, spaces, and of course tab characters.

To illustrate this idea, an example string "Hello" will be used, in which has two TAB characters embedded:

The table below holds the results after calling ScriptShape and ScriptPlace on this text-string:

Array | [0] | [1] | [2] | [3] | [4] | [5] | [6]
pwGlyphs[] | 43 | 3 | 72 | 79 | 3 | 79 | 82
piAdvance[] | 165 | 0 | 102 | 64 | 0 | 64 | 115

Notice that the tab-characters have both been represented by a glyph-index of "3". Although this glyph-index is only valid for a specific font, it represents the 'non-displaying' glyph - that is, a glyph with no visual representation. More interesting though is the resulting widths of these 'invisible' glyphs, which are initially set to zero "0".

The normal course of action once we have got to this stage is to call ScriptTextOut, with the generated widths+glyphs shown above. This would result in the following:

The dotted-outline is purely used here to bring across the concept of each glyph being an individual entity. Also notice the two vertical bars which are supposed to represent the (currently) zero-width tab characters.

The process of tab-expansion is straight-forward. All we need to do is to modify the individual width-entries for tabs inside the width-list. Once this is done all drawing and mouse hit-testing will use the modified glyph-widths, resulting in extra space being allocated where the tab characters would be.

Tab-expansion must obviously occur after ScriptShape and ScriptPlace have been called. After all item-runs have been processed in this way, UspAnalyze calls another internal function - ExpandTabs:

?
BOOL ExpandTabs(USPDATA *uspData, WCHAR *wstr, int wlen, SCRIPT_TABDEF *tabdef);

SCRIPT_TABDEF is a standard Uniscribe structure used for ScriptStringAnalyze. It contains information about the tab-stops in a string (size and locations). I have used this same structure for UspLib purely to be consistent.

Applying Attributes

UspLib supports variable length attribute-runs when styling a string of Unicode text, using an array of ATTR structures. Although Neatpad does not take advantage of this facility (it just sets each ATTR to "1" unit long), the possibility still exists for variable-length runs to be specified.

Whilst this is not a problem in itself, processing variable length style-runs at the same time as displaying runs of glyphs can get very complicated. To simplify this matter UspLib always flattens any user-supplied attribute-run, and keeps an internal copy inside the USPDATA object. The flattened run-list is allocated to the same length as the original Unicode string, and contains exactly one ATTR structure per original Unicode character.

?
UspApplyAttributes(USPDATA *uspData, ATTR *attrRunList)

The UspApplyAttributes (above) is used to update the style-run information belonging to a USPDATA object, and is called by UspAnalyze as part of the string-analysis process. However this function can be called at any time after a string has been analyzed. Note that only the colour-information is updated on subsequent calls to UspApplyAttributes - as reapplying font information would require the entire string to be re-analyzed.

UspAnalyze

We have now covered enough ground to complete the implementation of UspAnalyze. All of the related code for this analysis phase is located in the UspLib.c file. The functional break-down of the analysis is shown below.

The result of all this work is a single USPDATA object, which contains all of the information necessary to display a string of Unicode text.

?
typedef struct _USPDATA
{
  //
  // Item-run information
  //
  int itemRunCount;
  ITEM_RUN       * itemRunList;
  int * visualToLogicalList;
  //
  // Logical character/cluster information (1 unit per original WCHAR)
  //
  int stringLen;            // length of current string (in WCHARs)
  WORD * clusterList;          // logical cluster info
  ATTR           * attrList;             // flattened attribute-list
  //
  // Glyph information for the entire paragraph
  // Each ITEM_RUN references a position within these lists:
  //
  int glyphCount;           // count of glyphs currently stored
  WORD * glyphList;
  int * widthList;
  GOFFSET        * offsetList;
  SCRIPT_VISATTR * svaList;
  //
  // external, user-maintained font-table
  //
  USPFONT        * uspFontList;
} USPDATA, *PUSPDATA;

The listing above details the USPDATA structure. For the purposes of clarity I have omitted several 'house-keeping' fields which are not required for this discussion.

One of the major difficulties when dealing with Uniscribe is knowing what to do with the huge amount of information that is generated. The strategy that I have taken with UspLib is to keep all information inside the USPDATA object. The "per-run" glyph information is concatenated into several large buffers (glyphList, widthList etc). Each ITEM_RUN refers to a certain range of data within each of these large buffers, using the ITEM_RUN::glyphPos and ITEM_RUN::glyphCount fields.

There are basically two approaches with Uniscribe - and can be categorized as Speed vs Memory consumption. The first strategy is to gather together all the information generated by the Uniscribe APIs into one object. This has the advantage of being quick in operation, because the 'analysis' phase (itemization, shaping etc) happens one time only. After this the glyph data is stored away and then reused each time the text is displayed.

The other approach is to conserve memory, by only allocating buffers when necessary, and repeatedly calling ScriptShape/Place each time glyph information is required. The advantage has already been mentioned, but the disadvantage is performance loss. Re-shaping item-runs each time they are displayed will be quite alot slower - and considering that a text-editor will need to redraw it's display every time the mouse-selection changes, this strategy is something that I want to avoid.

For UspLib I have opted for the speed (resource-heavy) approach.

Coming up in Part 14

We still haven't drawn any text but it won't be long before we do. The next tutorial will focus on the UspTextOut function, and will demonstrate how to display styled Unicode text by taking the output from ScriptShape and ScriptPlace, and applying the attribute-runs stored in the USPDATA object.

‹ Uniscribe Mysteries up Drawing styled text with Uniscribe ›

2 Mar 2006

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Integrating UspLib | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
neatpad15.zip

Published on 22 Jan 2012

153.23 Kb

Download

Integrating UspLib

Design & Implementation of a Win32 Text Editor

It's finally here! - a new and improved Neatpad which demonstrates the rendering capabilities of UspLib. The purpose of this is tutorial to document the UspLib API, and secondly to mention a few details about how UspLib was integrated into Neatpad's code. I very much hope that the design of UspLib is good enough that it will others to import it into their own editors and get instant styled-text support!

The image above shows Neatpad's new Unicode text-rendering engine in action. Five different scripts are being displayed - Devanagari, Tamil, Thai, Arabic and of course Latin. Font-fallback is not currently supported in Neatpad, so to display all of these different scripts a suitable font must be selected. In the example above I used the "Arial Unicode MS" font which weighs in at a hefty 22Mb!

Now, don't get too exited about this latest version. On the surface it is no different than before - it is only until you load a Unicode file containing lots of complex scripts that you will see where all the work has gone into.

The UspLib API has been documented below. Please let me know if you were successful in integrating this API into your own projects!

To use UspLib, include the single header-file usplib.h, and link against usplib.lib. There are no dependencies on the library itself other than the Uniscribe Script Processor DLL (usp10.dll) which will be present on Windows2000 and above.

UspAllocate

?
USPDATA * UspAllocate();

UspAllocate initializes and returns a new USPDATA object, which must be used for all subsequent UspLib operations.

UspAnalyze

?
BOOL UspAnalyze (
  USPDATA         * uspData,
  HDC hdc,
  WCHAR * wstr,
  int wlen,
  ATTR            * attrRunList,     // optional
  UINT flags,
  USPFONT         * uspFont,         // optional
  SCRIPT_CONTROL  * scriptControl,
  SCRIPT_STATE    * scriptState,
  SCRIPT_TABDEF   * scriptTabdef,    // optional
);

UspAnalyze takes as input a single USPDATA object and analyses the the specified paragraph of UTF-16 text, saving the results back in uspData.

  • uspData points to a single USPDATA object which will hold the results of the analysis. This object can be reused from a previous call to UspAnalyze, which results in less memory allocation overheads.
  • hdc is a handle to a device-context.
  • wstr and wlen together identify the wide-character string to be analyzed.
  • attrRunList points to an optional array of ATTR structures. The size of this array is not directly specified in the call to UspAnalyze. The range of text represented by the ATTR::len field of each array element is assumed to be wlen - the same length as the string being analyzed.

    If attrRunList is NULL, the string is intialized with a single default attribute spanning the entire range of text, using the default system colours and the font specified by uspFont.

  • flags is a single DWORD variable which should be set to zero.
  • uspFont points to an optional array of USPFONT structures. Each array element must have been intialized using UspInitFont beforehand. If uspFont is NULL, the currently selected font in HDC is used instead. This same font must be re-selected into the target device-context when calling UspTextOut.
  • scriptControl points to an optional SCRIPT_CONTROL structure. See MSDN for details.
  • scriptState points to an optional SCRIPT_STATE structure. See MSDN for details.
  • scriptTabdef points to an optional SCRIPT_TABDEF structure, which defines the tab-stop positions to be used when performing tab-expansion. See MSDN for details.

UspAnalyze must be used to analyze an entire paragraph of text. The resulting USPDATA object can be used in subsequent calls to UspTextOut and UspSnapXtoOffset.

?
struct ATTR
{
   COLORREF fg;         // foreground text colour
   COLORREF bg;         // background text colour
   int len      : 16;    // length of this run (in WCHARs)
   int font     : 7;     // font-index into the USPFONT table
   int sel      : 1;     // selection flag (yes/no)
   int ctrl     : 1;     // show as an isolated control-character
   int eol      : 1;     // only valid for last character in line, prevents mouse selection
   int reserved : 6;     // unused
};

All fields of the ATTR structure must be initialized before use. Any unrequired field should be set to zero. The ATTR::font field is used as an index into the USPFONT table. Any font in referenced by ATTR::font must have initialized using UspInitFont.

UspInitFont

?
void UspInitFont (
  USPFONT  * uspFont,
  HDC hdc,
  HFONT hFont
);

UspInitFont must be called once for each font referenced by UspAnalyze in the attrRunList array. Several font-related resources are managed by the USPFONT object, including the Uniscribe SCRIPT_CACHE object, and the text-metrics for the font.

  • uspFont points to a single USPFONT structure.
  • hdc is a handle to a device-context.
  • hFont is a handle to the font resource.

The USPFONT structure is defined below:

?
struct USPFONT
{
  HFONT hFont;
  SCRIPT_CACHE  scriptCache;
  TEXTMETRIC    tm;
  int yoffset;      // height-adjustment when drawing font (set to zero)
};

The yoffset field is user-defined and specifies the vertical adjustment to be applied to all text using this font. UspInitFont initially sets this value to zero, however it can be modified after this call. All other structure members are managed by UspInitFont and should not be modified by the caller.

UspFreeFont

?
void UspFreeFont (
  USPFONT  * uspFont
);

UspFreeFont must be called when the specified USPFONT resource is no longer required. The font-handle specified in the call to UspInitFont is released, as well as the SCRIPT_CACHE object held internally to the structure.

UspApplyAttributes

?
void UspApplyAttributes (
  USPDATA  * uspData,
  ATTR     * attrRunList
);

UspApplyAttributes can be called at any time to re-apply the style-run attributes for the specified USPDATA object. Only the colour and selection information is used - all other fields of the attribute-runs (including the font) are ignored.

  • attrRunList specifies a new list of style-runs for the text.

The attribute-run list must reference a range of text the same length as the string that was previously analyzed by UspAnalyze.

UspApplySelection

?
void UspApplySelection (
  USPDATA  * uspData,
  int selStart,
  int selEnd
);

UspApplySelection performs a similar task to UspApplyAttributes. However this time only the selection-flags are modified in the USPDATA object.

  • selStart is the starting position in the string where the selection-highlight should begin.
  • selEnd is the ending position of the selection-highlight.

UspSetSelColor

?
void UspSetSelColor (
  USPDATA   * uspData,
  COLORREF fg,
  COLORREF bg
);

UspSetSelColor controls the selection-highlight colour to be used when calling UspTextOut. Any character marked with the ATTR::sel attribute, or any range of text identified by UspApplySelection will be drawn using this colour. Note that by default, the Windows selection-highlight colours will be used.

  • fg is the COLORREF value of the selection foreground (text) colour.
  • bg is the COLORREF value of the selection background colour.

UspTextOut

?
int UspTextOut (
  USPDATA  *  uspData,
  HDC hdc,
  int xpos,
  int ypos,
  int lineHeight,
  int lineOffsetY,
  RECT     *  rect
);

UspTextOut is the counterpart to ScriptStringOut. It takes as input the USPDATA object which was previously analyzed, and draws the text to the specified location. Any fonts, colours and selection-highlights are applied to the text as it is drawn.

  • hdc is a handle to a device-context.
  • xpos is the x-coordinate where text-output should begin.
  • ypos is the y-coordinate where the text-output should begin.
  • lineHeight specifies the total height, in pixels, that will be occupied by each line. The text background will be filled to this extent. Applications will usually set this value to be the same as (rect.bottom - rect.top)
  • lineOffsetY specifies the vertical distance in pixels - relative to ypos - from which the text will be offset. This value is in addition to any y-adjustment specified by the USPFONT structures. Can be zero.
  • rect is the bounding rectangle beyond which clipping will occur. This parameter must be specified and at a minimum should identify the client-area rectangle of the device-context.

It is recommend to "double-buffer" the output of this function as the multi-pass rendering will result in flickering. The alignment-mode, background-mode and device-context colours of the device-context are unspecified on this function's return.

UspTextOut will change in the future to support word-wrapping.

UspSnapXToOffset

?
BOOL UspSnapXToOffset (
  USPDATA    * uspData,
  int xpos,
  int * snappedX,     // out, optional
  int * charPos,      // out
  BOOL * fRTL          // out, optional
);

UspSnapXtoOffset converts an x-coordinate to the nearest character-offset. In addition it returns the x-coordinate of the selected character.

  • xpos specifies the x coordinate.
  • snappedX points to an integer that receives the adjusted x-coordinate.
  • charPos points to an integer that receives the character position corresponding to xpos.
  • fRTL points to a BOOL that receives the direction of the item-run corresponding to xpos. If TRUE it indicates a right-to-left run, if FALSE it indicates a left-to-right run.

The fRTL parameter is useful for the case when the text-caret's shape is modified to reflect the reading-direction of the run of text that corresponds to xpos.

UspXToOffset

?
BOOL UspXToOffset (
  USPDATA    * uspData,
  int xpos,
  int * charPos,      // out
  BOOL * trailing,     // out
  BOOL * fRTL          // out, optional
);

UspXToOffset converts an x-coordinate to a character position.

  • xpos specifies the x coordinate.
  • charPos points to a variable that receives the character position corresponding to xpos.
  • trailing points to a variable that receives an indicator whether the position is the leading or trailing edge of the character.
  • fRTL points to a variable that receives the direction of the item-run corresponding to xpos. If TRUE it indicates a right-to-left run, if FALSE it indicates a left-to-right run.

UspOffsetToX

?
BOOL UspOffsetToX (
  USPDATA    * uspData,
  int offset,
  BOOL trailing,
  int * xpos          // out
);

UspOffsetToX returns the x-coordinate for the leading or trailing edge of a character position.

  • offset specifies the character position in the string.
  • trailing indicates the edge of the character that corresponds to the x coordinate. If TRUE it indicates the trailing edge, if FALSE it indicates the leading edge.
  • xpos points to a variable that receives the corresponding x coordinate for the character-offset.

UspFree

?
void UspFree(USPDATA * uspData);

UspFree should be called when the specified USPDATA object is no longer required.

Changes to Neatpad

It was very straight-forward to import UspLib into Neatpad's existing codebase. However there were several changes made to key aspects of the TextView library which made this possible. These changes are briefly mentioned below.

  • Reference to NeatTextOut and NeatTextWidth have been removed, as have the functions themselves. This means that all previous tutorials that discussed drawing and painting (prior to UspLib) are effectively obsolete. Although the ideas they presented were good, the method in which styled text was drawn (successive calls to ExtTextOut) have been superseded by the UspLib library.
  • The existing mouse-handling code has been substantially reduced in complexity. The caret hit-testing ideas presented in previous tutorials have again been superseded by the functionality provided by UspLib.
  • Font-handling has been moved in part to UspLib.
  • Control-character rendering is fully handled by UspLib so all of the related code has been removed from the TextView.

Whilst a large amount of code has removed from the TextView, in reality these areas of functionality have been transferred to UspLib which now handles all aspects of drawing, fonts and mouse hit-testing.

UspLib was designed primarily for use with Neatpad. However this does not mean that it cannot be used for other text-editor projects, or in fact any application that requires the use of complex, styled text. Remember, UspLib is Freeware and can be used in any project!

Problems with paragraphs

With Uniscribe (and therefore UspLib), the basic unit of text is the paragraph. For text-editors such as Neatpad, an entire line can be treated as a paragraph. This concept is important however, as it imposes a restriction on how UspLib should be used. Because whole lines must be analyzed, this effectively means that an entire line of text must be in memory at one time. The consequence of this means that we must impose a "line length" limit on text files that we load. In Neatpad, any line of text beyond a certain length will be truncanted.

I'm quite please about this limitation actually as I wasn't looking forward to handling arbitrarily long lines. These are just a few of the issues that long-lines present:

  • How to apply the Unicode bi-directional algorithm with ScriptItemize when the whole line must be in memory?
  • What would the maximum line-length be anyway? 2Gb. 4Gb?
  • How would we represent x-coordinates on a line this long? The x-coordinate would overflow the limits of a 32bit integer.
  • How would we count the characters on a line that contained tabs? Tab-expansion could potentially push the line-length beyond 4Gb.

I don't have any good answers to these questions so I'm happy for the moment to have a simple restriction of something like 65Kb per line. I'd like to hear any thoughts in this area though!

Caching with GetUspData

The big issue with Uniscribe is all the memory that must be allocated in order to display just a single line of text. UspLib hides this complexity behind the USPDATA object. However the memory overhead that each USPDATA imposes is quite significant:

16 bytes per glyph.14 bytes per wide-character.32 bytes for each item-run.

For a typical string of UTF-16 text we are looking at an increase of many times that of the original string length. Obviously this is far too much to be creating USPDATA objects for every line of text in a file. To solve this problem a new TextView member function was written, which manages USPDATA objects from an internal cache.

?
struct USP_CACHE
{
   USPDATA * uspData;      // the UspLib data for this line
   ULONG lineno;       // which line this refers to
   ULONG usage;        // usage count for caching purposes
};
class TextView
{
   ...
   // keep an internal cache of USPDATA objects
   USP_CACHE m_uspCache[USP_CACHE_SIZE];
};

Whenever a line of text is required by the TextView (for drawing or mouse hit-testing), the GetUspData function is called. The drawing and mouse-related routines no longer directly access the underlying TextDocument. All data-access is now through this single function.

?
USPDATA *TextView::GetUspData(HDC hdc, ULONG nLineNo)
{
    TCHAR buff[TEXTBUFSIZE];
    ATTR      attr[TEXTBUFSIZE];
    int len;
    USPDATA * uspData = << find a cached object >>
    // if found a match (an already analyzed line) then return it here!!
    if(....)
        return uspData;
    // otherwise we need to style + analyze a new line
    len = m_pTextDoc->getline(nLineNo, buff, TEXTBUFSIZE, &off_chars);
    len = ApplyTextAttributes(nLineNo, off_chars, colno, buff, len, attr);
    // setup the tabs
    int tablist[]       = { m_nTabWidthChars };
    SCRIPT_TABDEF   tabdef          = { 1, 0, tablist, 0 };
    SCRIPT_CONTROL  scriptControl   = { 0 };
    SCRIPT_STATE    scriptState     = { 0 };
    // generate glyphs etc
    UspAnalyze(uspData, hdcTemp, buff, len, attr, 0, m_uspFontList,
       &scriptControl, &scriptState, &tabdef);
    return uspData;
}

The sample-code above gives the general idea for how GetUspData works. The caching details are rather boring so I've omitted them here (just look in the real sources). The idea behind this method though, is that any time we want a USPDATA object, GetUspData will return one ready-analyzed. Most of the time this object will be from the cache, and only occasionally will a line need to be fetched from the TextDocument and analyzed with UspAnalyze.

Conclusions

The move to Uniscribe defines a turning-point in Neatpad's development. It has taken alot of effort to get here but the future now looks alot clearer. In many ways I wish I had started this project with Uniscribe right from the beginning - it would have saved alot of work. However Unicode is quite complicated and I think the beginning tutorials would have suffered from this extra complexity. Besides, I think it is good to see the evolution that has occurred since the start of this project, and also the mistakes that I have made along the way.

Overall I've found working with Uniscribe to be a very rewarding experience. The API itself is rather complicated but it is very well designed. The main difficulty is coming to terms with the concept of glyph-based rendering. However I do feel that the MSDN documentation for Uniscribe to be rather inadequate in places. For someone who had no prior experience in displaying Unicode text I struggled for quite some time before finally completing this phase of the project.

As a comparison, take a look at the Apple documentation for ATSUI (an equivalent API to Uniscribe but higher-level). The documentation is much clearer in my opinion - it doesn't just document the ATSUI API but gives guidelines on how it should be used on the Apple system.

Coming up in Part 16

There are still some minor "todo's" with UspLib which I haven't quite managed to finish. The issue of CRLF sequences at the end of a line of text needs addressing for bi-directional texts. Sometimes the CRLF will not be on the far-right of a line - for RTL texts it can be on the left-side, or even in the middle of the line! The other issue is properly displaying the file with full right-to-left alignment, with the scrollbar positioned on the left.

The next tutorial will look at adding keyboard support to the TextView. We will focus only on caret-movement with the keyboard, as actual text-entry must wait until the TextDocument can actually edit text. The caret-movement code will be using Uniscribe's ScriptBreak routine, which will probably result in a couple more UspLib functions being added.

Beyond this I will probably tackle syntax highlighting, and once the GUI is completely finished I will finally move onto file-editing. The end is getting alot closer now I feel!

‹ Drawing styled text with Uniscribe up Keyboard Navigation ›

6 Mar 2006

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Drawing styled text with Uniscribe | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
uspdemo.zip

Published on 22 Jan 2012

39.63 Kb

Download

Drawing styled text with Uniscribe

Design & Implementation of a Win32 Text Editor

The last tutorial saw the completion of the UspAnalyze function, one of the main APIs of the new UspLib text-rendering engine. We will now switch our attention to the implementation of the UspTextOut function. Our goal is to divide up the glyph-lists we ceated in the last tutorial, and apply colour information prior to display with ScriptTextOut. The method we will use to identify which colour belongs to each glyph is the central theme of this tutorial.

Now, there is alot of very specific information in this tutorial related to Unscribe and you are only going to find it interesting if you have also been trying to understand how to draw styled text. So feel free to skip to the next tutorial if you want to see UspLib in action.

The image above shows another small utility I wrote whilst working with Uniscribe. The purpose of this app is to demonstrate (and test) the UspLib library. You can download the demo, and also the UspLib sourcecode, at the top of this article.

6. Drawing styled text

At this point we could quite simply call ScriptTextOut with a whole run of glyphs and be done with it. It would display correctly and we would have succeeded in our goal to display Unicode text. However the text would only be drawn in a single font and colour, and it would have been much simpler to use the ScriptString API instead! Remember, the entire reason we are looking at Uniscribe is because we need to apply font and colour information in a very fine-grained manner.

Back in part#10 of this series I proposed a new method for rendering text in Neatpad, using three separate passes. I have implemented this rendering scheme with the UspTextOut function:

?
void UspTextOut( USPDATA  *  uspData,
                 HDC hdc,
                 int xpos,
                 int ypos,
                 RECT     *  bounds
    )
{
    //
    //  1. Draw all background colours, including selection-highlights;
    //     selected areas are added to the HDC clipping region which prevents
    //     step#2 (below) from drawing over them
    //
    PaintBackground(uspData, hdc, xpos, ypos, bounds);
    //
    //  2. Draw the text normally. Selected areas are left untouched
    //     because of the clipping-region created in step#1
    //
    SetBkMode(hdc, TRANSPARENT);
    PaintForeground(uspData, hdc, xpos, ypos, bounds, FALSE);
    //
    //  3. Redraw the text using a single text-selection-colour (i.e. white)
    //     in the same position, directly over the top of the text drawn in step#2
    //     Before we do this, the HDC clipping-region is inverted,
    //     so only selection areas are modified this time
    //
    PaintForegound(uspData, hdc, xpos, ypos, bounds, TRUE);
}

UspTextOut is quite similar to ScriptStringOut, in that it requires a string to be analyzed prior to display. It takes as input the USPDATA object that contains the information generated by UspAnalyze. Although there are three passes involved, there are only two functions that need to be implemented - DrawBackground, and DrawForeground which will be used to draw both regular (styled) as well as 'selected' text. We will take a look at the implementation of these functions a little further down.

Characters vs Glyphs vs Clusters

The major problem with Uniscribe is understanding how to decipher the results of the ScriptShape and ScriptPlace calls. There is just so much information returned about each run of text that it takes a fair amount of time and effort to understand it all. Hopefully by the end of this tutorial you will have a little more insight into how all of the Uniscribe functions hang together.

The key detail to understand about Uniscribe (and computer Typography in general) is the difference between characters and glyphs. Up until this point the main focus with Neatpad has been logical Unicode character sequences. However once Uniscribe has been involved the focus is very much on glyphs. The thing to understand here, is that there is no direct relationship between characters and glyphs.

For simple scripts such as English a font usually contains one glyph per Unicode character. However for more complex scripts this relationship can change. Sometimes a single Unicode character can result in more than one glyph. The opposite is also true - there can also be multiple Unicode characters resulting in just a single glyph. This behaviour various depending on what font is being used. This separation between characters and glyphs presents a problem because our attribute style-runs are all character based, and we somehow need to translate this styling information onto specific glyphs.

To make things even more complicated, the concept of glyph clusters must be understood. A cluster is basically a grouping of glyphs which must be treated as a single selectable unit. Whilst this is not a problem in itself, it does make rendering glyph sequences a little more complicated because cluster boundaries must be respected.

Understanding the Logical Cluster List

The logical-cluster list is key to establishing a relationship between characters and glyphs. This list is returned by ScriptShape in the pwLogClust[] array. It provides the mapping between logical character-positions and glyph-cluster positions. UspLib stores each run's logical-cluster information inside the clusterList[] field of the USPDATA object.

To support this idea of character-to-glyph mapping, the logical-cluster list must represent two important concepts:

  • Firstly, it identifies the cluster boundaries in the original Unicode string - that is, the offsets in logical character units (WCHARs) of each cluster. Each entry in the clusterList corresponds exactly to a single character in the original string - so clusterList is always the same length as the Unicode string we are processing.
  • Secondly, this same array also identifies the offsets of each glyph-cluster, within the glyph-buffers generated by ScriptShape and ScriptPlace.

In other words, the individual element values (the content) of the clusterList defines the glyph-clusters, whilst the positions of the array elements represents the clusters in logical character terms.

As an example we will use the same Arabic string "يُساوِي" we were looking at previously. This string of seven Unicode characters results in the following logical cluster information being generated by ScriptShape. Note that the logical array-index positions are listed across the top of the table.

Array | [0] | [1] | [2] | [3] | [4] | [5] | [6]
WCHAR wszText[] | U+064A | U+064F | U+0633 | U+0627 | U+0648 | U+0650 | U+064A
WORD clusterList[] | 6 | 6 | 4 | 3 | 2 | 2 | 0

Whole clusters are identified by grouping together any identical numbers in the logical-cluster list. As you can see from the cluster-list in the table above, there are two 6's and two 2's (in addition to the other singlular numbers), resulting in a total of five whole clusters all together. The image below illustrates this grouping concept.

Notice that cluster-list is always stored in logical order, whilst the glyph-list is always in visual order. This means that for right-to-left scripts (such as the Arabic string above), the cluster-list elements will decrease when reading the array. As a result of this, the first glyph that must be drawn will be at the very end of the glyph-list. Bearing this in mind, the breakdown of the clusters is as follows:

  • 1st cluster: two characters represented by two glyphs.
  • 2nd cluster: one character represented by one glyph.
  • 3rd cluster: one character represented by one glyph.
  • 4th cluster: two characters represented by two glyphs.
  • 5th cluster: one character represented by one glyph.

Hopefully it should be fairly obvious how the logical clusters were identified - with the number of WCHARs in each cluster calculated by the number of characters in each grouping. Calculating the number of glyphs in each cluster is less obvious. The key here is looking at the difference between the cluster values. This is how the identification of each cluster occurred:

  1. The first two 6's identify cluster#1, comprising two WCHARs (in character-positions 0 and 1). This value of 6 points to the end of the glyphList which contains the glyphs for this cluster. We know that this cluster is represented by two glyphs (#5 and #6) because:
  2. The next value in the cluster-list (4) tells us two things. Obviously this cluster starts at glyph #4 in the glyph-list. However this also means that there were 2 (two) glyphs in the last cluster (6-4 = 2).
  3. The third cluster is comprised of the single glyph#3, and a single WCHAR.
  4. The fourth cluster is comprised of two WCHARs again, which are represented by glyphs #1 and #2.
  5. The fifth and final cluster is again a single WCHAR, represented by glyph#0 in the glyph-list.

As you can see the key detail here is looking at the difference between glyph-indices in order to count the number of glyphs in each cluster. Special consideration must also be taken with right-to-left scripts because of the way that glyphs are stored in reverse order. The way I handled this was to advance the x-coordinate to the end of the run, call SetTextAlign(TA_RIGHT), and then move the output-location to the left each time, resulting in the glyphs being output in logical (right-to-left) order.

The important thing to understand is that we always follow the cluster-list in logical order, even for right-to-left scripts. We rely on the ordering of the element values to locate each glyph-cluster as it should be drawn.

Another example

The previous example was of couse a right-to-left script and highlighted the unique way in which these scripts are represented by Uniscribe. The example shown next is based on the example in MSDN under the ScriptShape documentation, and highlights how complex left-to-right text is represented by Uniscribe.

U+920, U+911, U+915, U+94D, U+937, U+91D, U+949

The string this time is from the Devanagari script. I have no idea what it means because I just strung a sequence of code-points together which happened to have the right "look". If anyone can supply me with a Unicode phrase of 7 characters, which results in the glyph+cluster properties shown below, then please get in touch!

Array | [0] | [1] | [2] | [3] | [4] | [5] | [6]
Unicode string | U+0920 | U+0911 | U+0915 | U+094D | U+0937 | U+091D | U+0949
clusterList[] | 0 | 1 | 4 | 4 | 4 | 5 | 5

The key difference here is how the cluster-list elements increase when reading the array. For left-to-right runs, the glyphs are stored in the same order as the original Unicode characters. This is the ordering that many Western readers will find most natural.

The diagram hopefully again illustrates the relationship between logical characters and glyph-clusters, this time for a left-to-right run of text. This example is purely fictitious as I couldn't find any phrase, font & script which satisfied the required glyph+cluster properties. Again, the number of glyphs per cluster is calculated by the difference between the cluster-list elements.

  • 1st cluster: one character represented by one glyph (1-0=1)
  • 2nd cluster: one character represented by three glyphs (4-1=3)
  • 3rd cluster: three characters represented by one glyph (5-4=1)
  • 4th cluster: two characters represented by three glyphs (8-5=3)

The number of glyphs for the last cluster was calculated because we knew how many glyphs (8 in total) were generated for this run by ScriptShape.

Interpolation is the key

The one thing to understand about Uniscribe is the separation between characters and glyphs. So looking now at another example, what happens when we have three characters comprising two glyphs? The problem we face is, how do we distribute the colour information for each character across the glyphs, and which glyph takes which colour?

U+0635 U+0651 U+0650

For some scripts where the glyphs typically order horizontally you can almost infer the colour relationships. However when glyphs stack vertically on top of each other within a cluster, or when there is an unequal number of characters-to-glyphs, there is no easy way to associate a colour with a particular glyph.

With UspLib I solved this problem in two ways - using one method for drawing the background, and another when drawing the actual glyphs themselves (the foreground). Drawing the foreground was easy: I decided simply to paint all glyphs in a cluster as a single colour. Should there be multiple colour-attributes for the cluster, only the first is chosen and the rest are ignored. This is by far the easiest method and in reality you wouldn't expect individual glyphs to have their own colours when in a cluster.

Painting the background is rather different, because the inversion-highlighting scheme must be taken into account. The strategy I have used here is to interpolate the colours across the width of each cluster, when drawing the background. This method is hinted at by Microsoft in the following quote from MSDN, in the section "notes on ScriptXtoCP and ScriptCPtoX":

"Cluster information in the logical cluster array is used to share the width of a cluster of glyphs equally among the logical characters they represent."

It took me a while to make the logical leap that this strategy could also be used for text-rendering, however after implementing it I realised it is the exact same method used by the ScriptString API.

The process is very simple. We know how many characters make up each cluster, and also how many glyphs make up each cluster. We therefore sum the width of these glyphs to calculate the total width of the cluster. We then divide the cluster-width by the number of characters in the cluster - this number tells us how wide each colour band should be.

?
advanceWidth = clusterWidth / charCount;

For some scripts, dividing the clusters this way makes alot of sense, especially for Arabic because the caret is conventially positioned at character boundaries rather than glyph-cluster boundaries. However for most scripts this would be viewed as incorrect. Rather than having a special-case just for Arabic, I have instead written UspTextOut so that it always interpolates over glyph-clusters, should any colour-attributes happen to be this fine-grained. We will rely on the fact that ScriptCPtoX will only allow the caret (and therefore selection-highlghts) to be placed in the middle of clusters when appropriate.

Lastly, using integer math for the cluster-division will be result in potential rounding errors. Whilst this is not a massive problem, we need to have the exact same results that ScriptCPtoX produces when it does its own presumed division-operations (otherwise we could be out by a pixel occasionally). Presumably ScriptCPtoX uses MulDiv in its calculations because this appears to give the correct results, and is what I have used for UspLib.

Drawing the background

As mentioned above, drawing the background is a little different because of the use of interpolation. We will start by looking at the PaintBackground routine:

?
void PaintBackground(USPDATA * uspData, HDC hdc, int xpos, int ypos, RECT * bounds)
{
    int i;
    ITEM_RUN  * itemRun;
    // Process the item-runs in visual-order
    for(i = 0; i < uspData->itemRunCount; i++)
    {
        itemRun = GetItemRun(uspData, i);
        // paint the background of the specified item-run
        PaintItemRunBackground(uspData, itemRun, hdc, xpos, bounds);
        xpos += itemRun->width;
    }
}

As you can see this function is very simple. It merely processes the item-runs in visual-order and advances the x-coordinate by the item-width for each run. Each item-run background are rendered individually by the PaintItemRunBackground function.

?
void PaintItemRunBackground(USPDATA *uspData, ITEM_RUN *itemRun, HDC hdc, int xpos, int ypos)
{
    int i, lasti;
    // locate the item-run buffers
    WORD * clusterList  = uspData->clusterList  + itemRun->charPos;
    ATTR  * attrList     = uspData->attrList     + itemRun->charPos;
    int * widthList    = uspData->widthList    + itemRun->glyphPos;
    for(lasti = 0, i = 0; i < itemRun->len; i++)
    {
        // search for a logical cluster boundary (or end of run)
        if(i == itemRun->len || clusterList[lasti] != clusterList[i])
        {
            << process cluster >>
        }
    }
}

The primary task is to identify the logical-cluster positions. The two loop-indices (lasti and i) represent these cluster positions in the original text-string. The number of WCHAR s in each cluster is therefore (i-lasti). Because we always iterate in logical order, this is true for both LTR and RTL texts.

?
<< process cluster >>
        int glyphIdx1, glyphIdx2;
        // locate glyph-positions for the cluster
        GetGlyphClusterIndices(itemRun, clusterList, i, lasti, &glyphIdx1, &glyphIdx2);
        // measure width of this group of glyphs
        for(runWidth = 0; glyphIdx1 <= glyphIdx2; )
            runWidth += widthList[glyphIdx1++];
        // divide the cluster-width by the number of code-points that cover it
        advanceWidth = MulDiv(runWidth, 1, i-lasti);

Once a cluster has been identified, GetGlyphClusterIndices is callled. This function inspects the clusterList and returns the corresponding glyph-index positions for i and lasti.

The width of the glyph-cluster is computed next, by simply iterating between glyphIdx1 and glyphIdx2, before dividing the cluster-width by the number of characters (WCHARs). We now know how far to advance each time we paint a bit of background.

?
for(a = lasti; a <= i; a++)
{
    // look for change in attribute background
    if(a  == itemRun->len           ||
       attr.bg  != attrList[a].bg   ||
       attr.sel != attrList[a].sel )
    {
        PaintRectBG(uspData, itemRun, hdc, xpos, &rect, &attr);
        rect.left = rect.right;
    }
}

The final task is to interpolate the colour-attributes over the cluster. We only ever paint the background if we detect a change in colour, so most of the time an item-run background is painted with just one operation. Missing from the code listing above is the small detail of correcting for rounding errors in the (integer) division - however this is not necessary for understanding the code.

I won't bother including the code for the PaintRectBG function - suffice to say it is not really very interesting, other than the fact that it calls ExcludeClipRect after drawing any selection-highlight background area.

?
void GetGlyphClusterIndices( USPDATA  * uspData,
                             ITEM_RUN * itemRun,
                             int clusterIdx1,
                             int clusterIdx2,
                             int * glyphIdx1,
                             int * glyphIdx2
                           )
{
    WORD *clusterList = uspData->clusterList + itemRun->charPos;
    // locate glyph-positions for the cluster
    if(itemRun->analysis.fRTL)
    {
        // RTL scripts
        *glyphIdx1 = clusterIdx1 < itemRun->len ? clusterList[clusterIdx1] + 1 : 0;
        *glyphIdx2 = clusterList[clusterIdx2];
    }
    else
    {
        // LTR scripts
        *glyphIdx1 = clusterList[clusterIdx2];
        *glyphIdx2 = clusterIdx1 < itemRun->len ? clusterList[clusterIdx1] - 1 : itemRun->glyphCount - 1;
    }
}

Above is the GetGlyphClusterIndices function. Note the two distinct cases for LTR and RTL scripts - this is required because the cluster-elements decrease when reading the cluster array (for RTL scripts), but increase for LTR scripts.

Drawing the Foreground

The process for drawing the text is so similar to that of the background that I won't bother including too much code this time. We'll jump straight in to the start of the DrawForegroundItemRun function:

?
// right-left runs can be drawn backwards for simplicity
if(itemRun->analysis.fRTL)
{
     oldMode = SetTextAlign(hdc, TA_RIGHT);
     xpos += itemRun->width;
     runDir = -1;
}

The first thing we do is set the text-alignment to TA_RIGHT for any right-to-left string, and advance the x-coordinate to the end of the run. This will allow us to draw the text in logical order (as we walk the logical-cluster-list). This is important because apart from this one detail, it means we can maintain a single function for drawing both LTR and RTL texts.

?
// loop over all the logical character-positions
for(lasti = 0, i = 0; i <= itemRun->len; i++)
{
    // find a change in attribute
    if(i == itemRun->len || attrList[i].fg != attrList[lasti].fg )
    {
        // scan forward to locate end of cluster (we must always
        // handle whole-clusters because the attr[] might fall in the middle)
        for( ; i < itemRun->len; i++)
            if(clusterList[i - 1] != clusterList[i])
                break;
       // locate glyph-positions for the cluster [i,lasti]
       GetGlyphClusterIndices(itemRun, clusterList, i, lasti, &glyphIdx1, &glyphIdx2);
       << display text >>
    }
}

The next difference between foreground and background rendering is how we identify cluster-boundaries. This time we look for changes in colour first of all. Once a new colour is found we scan forward to locate the end of the cluster. This means we can paint the whole cluster in one colour and not worry about interpolation.

?
<< display text >>
// measure the width (in pixels) of the run
for(runWidth = 0, g = glyphIdx1; g <= glyphIdx2; g++)
    runWidth += widthList[g];
// only need the text colour as we are drawing transparently
SetTextColor(hdc, forcesel ? uspData->selFG : attrList[lasti].fg);
//
// Finally output the run of glyphs
//
hr = ScriptTextOut(
    hdc,
    &uspFont->scriptCache,
    xpos,
    ypos,
    0,
    NULL,
    &itemRun->analysis,
    NULL,
    0,
    glyphList  + glyphIdx1,
    glyphIdx2  - glyphIdx1 + 1,
    widthList  + glyphIdx1,
    NULL,
    offsetList + glyphIdx1
);
// +ve/-ve depending on run direction
xpos     += runWidth * runDir;
lasti     = i;

Once the text-colour has been set, ScriptTextOut is called with the range of glyphs which fall within the cluster. Once again, we only output any text should there be a change in colour so usually there would only be one call to ScriptTextOut.

7. ScriptTextOut

For the sake of completeness here's the prototype for ScriptTextOut :

?
HRESULT WINAPI ScriptTextOut(
   HDC hdc,
   SCRIPT_CACHE     * psc,
   int x,
   int y,
   UINT fuOptions,
   // ExtTextOut options
   RECT             * rect,
   SCRIPT_ANALYSIS  * analysis,
   WCHAR * pwcReserved,
   int iReserved,
   WORD * pwGlyphs,     // in - results of ScriptShape
   int cGlyphs,
   int * piAdvance,    // in - results of ScriptPlace
   int * piJustify,
   GOFFSET          * pGoffset      // in - results of ScriptPlace
);

That's a pretty intimidating function by anyone's standards! The parameters of note are:

  • fuOptions is can be one of ETO_CLIPPED, ETO_OPAQUE, or zero. These are the standard ExtTextOut flags. Because we have drawn the background ourselves there is no need to use these parameters. Note that if opaque was specified, whole clusters of glyphs must be passed to this function.
  • pwGlyphs and cGlyphs identify the list of glyph values returned by ScriptShape.
  • piAdvance and pGoffset point to the glyph-placement buffers returned by ScriptPlace.
  • piJustify points to an optional array of justified advance values.

ScriptTextOut is basically a wrapper around ExtTextOut - however you will notice that there is no WCHAR* parameter to this function. This is because ScriptTextOut calls ExtTextOut with the ETO_GLYPH_INDEX option, and passes the buffer of glyphs we specifed.

ScriptTextOut may perform additional processing (such as glyph-reordering) before calling into GDI, so don't be tempted to bypass ScriptTextOut by calling ExtTextOut directly.

Uniscribe Limitations

One of the drawbacks of Uniscribe is the very thing it does best - the breaking up of a string into individually shapable items. The problem is that some strings containing alot of whitespace or punctuation result in a large number of item-runs. Whilst this is not bad in itself, it does present a problem when it comes to rendering the line of text. The shear number of calls to ScriptTextOut has a performance penalty - in comparison to calling ExtTextOut with the same line of text.

For complex-scripts there is no alternative but to break up the string using ScriptItemize. However it would be nice if for non-complex (i.e. English) scripts we could somehow re-combine the item-runs and reduce the potential number of calls to ScriptTextOut. I haven't ventured too far down this path yet, but it is certainly possible to identify if an item-run is complex or not by inspecting the SCRIPT_ANALYSIS::eScript field.

?
struct SCRIPT_ANALYSIS
{
    WORD eScript : 10;
    WORD fRTL    : 1;
    ...
};

Now, the eScript field is 'opaque' which means we shouldn't make any assumptions about its value. However it can be used as an index into the "global script table", which contains information about the specific script-shaping engines installed in a system.

?
HRESULT WINAPI ScriptGetProperties(SCRIPT_PROPERTIES ***ppSp, int *piNumScripts);

The ScriptGetProperties function returns a pointer to this global-script-table, and each entry in the table is a pointer to a SCRIPT_PROPERTIES structure:

?
struct SCRIPT_PROPERTIES
{
    DWORD langid;
    DWORD fNumeric;
    DWORD fComplex;
    ...
};

There are many information-fields in this structure, however the interesting one for us is the fComplex flag. Drawing all this together results in the following function, which returns a boolean indicating if an item-run is complex or not:

?
BOOL IsRunComplex(ITEM_RUN *itemRun)
{
    SCRIPT_PROPERTIES ** propList;
    int propCount;
    int scriptIndex;
    // get pointer to the global script table
    ScriptGetProperties(&propList, &propCount);
    // the SCRIPT_ANALYSIS::eScript is an index to the global script table
    scriptIndex = itemRun->analysis.eScript;
    // locate the script from the script-index
    return propList[scriptIndex]->fComplex;
}

Any non-complex item-runs could theoretically be identified and then merged together into a single run, with the SCRIPT_ANALYSIS::eScript field set to SCRIPT_UNDEFINED. All this should happen before ScriptShape is called.

Coming up in Part 15

Every time I post a new tutorial I promise that there'll be another update to Neatpad, and of course it hasn't happened (again!). Uniscribe is just so damn complicated it has taken me far more time to document than I first anticpated. For now you can download the UspLib demo at the top of this tutorial, and next time we really will be seeing a new-and-improved Neatpad.

‹ More Uniscribe Mysteries up Integrating UspLib ›

6 Mar 2006

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Keyboard Navigation | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
neatpad16.zip

Published on 22 Jan 2012

163.5 Kb

Download

Keyboard Navigation

Design & Implementation of a Win32 Text Editor

Keeping with the Uniscribe theme brings us to the next area of Neatpad's development that hasn't been touched on yet, which is keyboard-input. I've deliberately left this stage until now because I knew that without Uniscribe keyboard-navigation would be very difficult indeed. The problem with keyboard-handling is not how to process keyboard input (which is easy), but rather how to navigate through a Unicode file - taking into account combining sequences, surrogates, graphaeme clusters etc.

Up until this point the Uniscribe API has been used extensively provide text-rendering support. Fortunately Uniscribe can be used for more than just text output, and we will be looking in detail at the ScriptBreak API and how it can help us manage keyboard navigation.

Keyboard messages in Win32

All Windows programs receive keyboard input in the form of WM_KEYDOWN and WM_KEYUP messages. When a key is pressed, a series of WM_KEYDOWN messages are sent to an application's message-queue, and when the key is released a single WM_KEYUP message is sent. These two messages are relatively 'low level' but together still form the foundation of keyboard-input in Windows.

 | Key Pressed | Key Released
Normal Keystroke | WM_KEYDOWN | WM_KEYUP
System Keystroke | WM_SYSKEYDOWN | WM_SYSKEYUP

The table above summarises the two basic keyboard-input messages, and also their 'system' counterparts - WM_SYSKEYDOWN and WM_SYSKEYUP. These last two messages are seldom used by Windows programs and have no relevance to Neatpad's development so I won't bother describing them here.

The WM_KEYDOWN message is most commonly used by applications to detect when specific keys on the keyboard have been pressed. This is a good way to detect keys such as <control>, <shift> and arrow keys. However when it comes to text-entry, processing specific key presses is not actually the best way to go about things.

For example, when processing specific key presses, there is no easy way to determine the case of a letter. The user could have hit the 'A' key, but is this in lowercase or uppercase? All we know is the virtual-keystroke 'A' was entered, but we don't know anything about the state of the CAPSLOCK button or whether the user is holding the SHIFT key down. Obviously the actual character entered would be different depending on these factors - in this simple case it could be either 'a' or 'A'. Things get even more complicated when you move beyond English keyboards into the realm of Unicode, Input Method Editors and system locales, where multiple key-strokes can result in wildly different characters.

Fortunately there is another mechanism for handling character input in Windows - the WM_CHAR and WM_UNICHAR messages. These messages are specifically intended to represent characters rather keystrokes. Interestingly, WM_CHAR is not automatically sent to an application when a key is pressed on the keyboard. It is not until the TranslateMessage function is called inside an application's message-loop that the WM_CHAR message is dispatched.

?
while(GetMessage(&msg, 0, 0, 0) > 0)
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

Above is the standard message-loop of many Win32 programs. Most programmers probably copy+paste this loop straight into their code without giving it much thought, but the TranslateMessage function in particular has a very specific purpose. It translates certain messages (such as WM_KEYDOWN) into a series of corresponding WM_CHAR messages. TranslateMessage takes into account certain things such as the state of the SHIFT or CAPSLOCK keys, and also the current locale. Note that the WM_KEYDOWN message being translated is not modified in any way - instead a new WM_CHAR message is constructed by TranslateMessage and posted back into the current thread's message-queue for subsequent processing.

 | Characters | Dead Characters
UTF-16 Character | WM_CHAR | WM_DEADCHAR
UTF-32 Character | WM_UNICHAR |
Input Method Editor | WM_IME_CHAR |
System Character | WM_SYSCHAR | WM_DEADSYSCHAR

The table above this time summarises the various character-input messages available to Windows programs. We don't be performing actual data-entry into Neatpad until much later on in this series so there is no point in looking at these messages now.

Keyboard Navigation with WM_KEYDOWN

The purpose of this tutorial is to cover the implementation of Keyboard Navigation - so we are only interested in physical keys such as the arrow-keys, page-up, page-down, home & end, etc. Therefore will only need to handle the WM_KEYDOWN message at this time - actual character-input (and the WM_CHAR /WM_UNICHAR messages) will wait until later in this series until we actually have a mechanism to modify the TextDocument.

In general, keyboard navigation in Windows text-editors is fairly consistent. The arrow keys (left, right, up, down) are used to move the text-caret in these four basic directions, and page-up, page-down, home and end are all well established in what they should achieve. In addition holding the control or shift keys should modify the behaviour of whatever navigation key is being pressed at the time - the shift key being used to alter the text-selection as the cursor is being moved.

The table below summarises the behaviours that we wil be implementing in Neatpad.

Key Code | Normal Action | TextView Method | With <Control> | TextView Method
VK_LEFT | Character left | MoveCharPrev | Word left | MoveWordPrev
VK_RIGHT | Character right | MoveCharNext | Word right | MoveWordNext
VK_UP | Line up | MoveLineUp(1) | Scroll line up | Scroll
VK_DOWN | Line down | MoveLineDown(1) | Scroll line down | Scroll
VK_PRIOR | Page up | MoveLineUp (x) |  |
VK_NEXT | Page down | MoveLineDown (x) |  |
VK_HOME | Line start | MoveLineStart | Document start | MoveFileStart
VK_END | Line end | MoveLineEnd | Document end | MoveFileEnd

Each action will be represented by a TextView member-function that will perform the associated operation. As you can see there are actually quite a number of different actions that we must implement. This is due in part to the effect of the control-key which almost doubles the number of methods we must implement.

The WM_KEYDOWN handler in Neatpad's TextView is shown below. A switch-statement is used to process each keystroke that we are interested in:

?
LONG TextView::OnKeyDown(UINT nKeyCode, UINT nFlags)
{
    bool fCtrlDown  = IsKeyPressed(VK_CONTROL);
    switch(nKeyCode)
    {
    case VK_LEFT:
        if(fCtrlDown)   MoveWordPrev();
        else MoveCharPrev();
        break;
    case VK_RIGHT:
    ...
    }
    << extend selection if <shift> is held down >>
    << update text-caret position >>
}

The purpose of the MoveXxxx functions is to update the m_nCursorOffset variable to reference a new position within the current file. Each MoveXxxx function adjusts m_nCursorOffset in a different way depending on what keyboard action should be processed. I won't include the entire function here because hopefully you can get the general idea from the snippet above.

Once a keypress has resulted in m_nCursorOffset being updated, the next step is to handle text-selections:

?
// Extend selection if <shift> is down
if(IsKeyPressed(VK_SHIFT))
{
    InvalidateRange(m_nSelectionEnd, m_nCursorOffset);
    m_nSelectionEnd = m_nCursorOffset;
}
// Otherwise clear the selection
else
{
    if(m_nSelectionStart != m_nSelectionEnd)
        InvalidateRange(m_nSelectionStart, m_nSelectionEnd);
    m_nSelectionEnd    = m_nCursorOffset;
    m_nSelectionStart  = m_nCursorOffset;
}

Extending the text-selection is simply a matter of checking the state of the shift key (up/down), and then modifying the m_nSelectionStart and m_nSelectionEnd TextView variables appropriately. When the selection should be extended (shift key is down) then only the m_nSelectionEnd variable is modified. Otherwise both variables are updated to the same value, effectively 'zeroing' the selection.

The final step is to update the physical caret-position from the cursor-offset. This is an important concept because at no time during the keyboard navigation does the caret's physical on-screen position need to be taken into account. All keyboard navigation is based soley on a single logical character-offset and it is only after the cursor-offset has been updated (due to a keypress) is the caret repositioned:

?
// update text-caret location (xpos, line#) from the offset
UpdateCaretOffset(m_nCursorOffset, &m_nCaretPosX, &m_nCurrentLine);

UpdateCaretOffset is already being used to position the caret from a previous tutorial (using the UspOffsetToX and SetCaretPos APIs), so this function is simply reused for our keyboard handling.

All keyboard navigation in Uniscribe is based around logical offsets. In other words, the cursor advances through the backing store (the file) in WCHAR units. When it comes to navigating through bidirectional strings the caret still advances logically through the file. We rely on Uniscribe converting the logical cursor-offset to a physical location on screen (using the ScriptCPtoX function). This may mean that the cursor appears to move both 'left' and 'right' within the same string even if a single arrow-key is being used. Don't worry about displaying the caret in bidirectional strings - because we are using Uniscribe it handles all these details for us automatically.

Lastly note the use of the IsKeyPressed function, which is a simple wrapper around the GetKeyState API. It's purpose is to simplify the test for whether a key is pressed or not and returns a boolean value indicating this fact.

?
bool IsKeyPressed(UINT nVirtKey)
{
    return GetKeyState(nVirtKey) < 0 ? true : false;
}

ScriptBreak

ScriptBreak works alongside ScriptItemize to identify the logical attributes of each character in a string. ScriptBreak must be called once for each individual item-run in the string (as identified by ScriptItemize) and returns an array of SCRIPT_LOGATTR structures. Each entry in the array represents a single WCHAR in the Unicode string, and must be allocated by the caller to have the same number of elements as there are WCHARs in the run.

?
HRESULT WINAPI ScriptBreak (
  WCHAR * pwcChars,
  int cChars,
  SCRIPT_ANALYSIS  * psa,
  SCRIPT_LOGATTR   * psla
);

The individual attributes for each character are held within the SCRIPT_LOGATTR structure, shown below:

?
struct SCRIPT_LOGATTR
{
  BYTE fSoftBreak   : 1;
  BYTE fWhiteSpace  : 1;
  BYTE fCharStop    : 1;
  BYTE fWordStop    : 1;
  BYTE fInvalid     : 1;
  BYTE fReserved    : 3;
};

Although each field of the SCRIPT_LOGATTR structure has a specific purpose, this information as returned by ScriptBreak is generally useful for two purposes: word-wrapping and keyboard navigation:

  • fSoftBreak indicates the positions within a string where word-wrapping can take place - in other words, the positions where the string can be broken into smaller units suitable for display over multiple lines.
  • fWhiteSpace indicates that the corresponding character should be treated as white-space. This could potentially be set for many more characters than just tabs and spaces.
  • fCharStop and fWordStop identify valid caret positions within the string. These positions can be used to support single character- based navigation and 'word' navigation.

Don't under-estimate just how much work ScriptBreak is doing on our behalf. The identification of character and word positions alone saves us a tremendous amount of effort. Added to this is the fact that ScriptBreak supports all of the various Unicode scripts, so for languages such as Thai (which require dictionary support to identify 'soft breaks'), all of the hard work is already done.

The task of calling ScriptBreak for each item-run is handled by the UspAnalyze function, which we looked at in previous tutorials. The SCRIPT_LOGATTR buffer is allocated and stored inside the USPDATA object's breakList * member. A simple loop is then used to iterate over each item-run, and the results of ScriptBreak stored inside the USPDATA::breakList array. The array holds the results for all item-runs, concatenated together:

?
<< UspLib.c - UspAnalyze(...) >>
// allocate memory for SCRIPT_LOGATTR structures
uspData->breakList = malloc(wlen * sizeof(SCRIPT_LOGATTR));
// Generate the word-break information for each item-run
for(i = 0; i < uspData->itemRunCount; i++)
{
    ITEM_RUN *itemRun = &uspData->itemRunList[i];
    ScriptBreak(
         wstr + itemRun->charPos,
         itemRun->len,
        &itemRun->analysis,
         uspData->breakList + itemRun->charPos
    );
}

Any string (or paragraph) of text analyzed with UspAnalyze will therefore automatically have it's SCRIPT_LOGATTR information stored inside the USPDATA object. Because the information for each run has been concatenated into the same buffer in effect individual item-runs do not need to be taken into account when inspecting the logical-attributes for each character in the string.

Let's look at a quick example and see how the string "Hello يُساوِي World" would be treated by ScriptBreak. Note that there are two spaces in the string, one either side of the Arabic phrase:

SCRIPT_LOGATTR | H | E | L | L | O |  | ي | ُ | س | ا | و | ِ | ي |  | W | O | R | L | D
SoftBreak | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0
WhiteSpace | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0
CharStop | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1
WordStop | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0
Invalid | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0

Tabs and Whitespace

ScriptBreak does not identify tab-characters as whitespace by default. This poses a problem because every text-editor under the sun understands that tabs are basically the same as spaces and should therefore be treated the same. The solution is to parse the Unicode string looking for tab-characters and modify the corresponding entries in the breakList :

?
for(i = 0; i < wlen; i++)
{
    if(wstr[i] == '\t')
        uspData->breakList[i].fWhiteSpace = TRUE;
}

The loop above can be found inside UspAnalyze and is executed after the ScriptBreak information has been obtained.

UspGetLogAttr

I have introduced a new UspLib function called UspGetLogAttr , which is similar in concept to the Script_pLogAttr function. It returns a pointer to the breakList buffer inside each USPDATA object. The difference is however, that a non-const SCRIPT_LOGATTR array is returned which can be modified by the caller of the function.

?
SCRIPT_LOGATTR * UspGetLogAttr( USPDATA * uspData )
{
    return uspData->breakList;
}

As you can see the UspGetLogAttr function is really very simple - all it does is return a pointer to the breakList buffer which is held inside each uspData object. Of course the real work in building this buffer was performed by the UspAnalyze function.

The reason this function exists is to allow the caller to modify the internal SCRIPT_LOGATTR structure inside each USPDATA object. This is important, because when it comes to syntax-highlighting I invisage that we will have to fine-tune the SCRIPT_LOGATTR buffer for each line to cater for more specific language-syntax details.

For now the UspGetLogAttr function is purely used by the keyboard navigation functions to control cursor-placement within each line of text.

Character and Word navigation

Character and Word navigation are quite closely related to each other. Both actions operate within a single line of text and both use the SCRIPT_LOGATTR information returned by ScriptBreak to position the caret at valid character/word positions:

?
VOID TextView::MoveCharPrev()
{
    USPCACHE         * uspCache;
    CSCRIPT_LOGATTR  * logAttr;
    ULONG lineOffset;
    int charPos;
    // get Uniscribe data for current line
    uspCache  = GetUspCache(0, m_nCurrentLine, &lineOffset);
    logAttr   = UspGetLogAttr(uspCache->uspData);
    // get character-offset relative to start of line
    charPos   = m_nCursorOffset - lineOffset;
    // find the previous valid character-position
    for( --charPos; charPos >= 0; charPos--)
    {
        if(logAttr[charPos].fCharStop)
            break;
    }
    << move up to end-of-last line if necessary >>
    // update cursor position
    m_nCursorOffset = lineOffset + charPos;
}

MoveCharPrev begins by obtaining the cached UspData object for the current line, and the SCRIPT_LOGATTR structure for that line is retrieved by calling UspGetLogAttr. The SCRIPT_LOGATTR array is then parsed to detect valid character-stop positions. Because UspData objects represent individual lines, all processing is relative to the start of each line:

?
// find the previous valid character-position
for( --charPos; charPos >= 0; charPos--)
{
     if(logAttr[charPos].fCharStop)
         break;
}

The loop above is quite simple. All it does is continue looping until a character-stop position is found. When the loop exits the charPos variable has been modified and the text-caret can be repositioned.

Word-navigation is a little more complicated. The MoveWordNext logic can be seen below:

?
// if already on a word-break, go to next char
if(logAttr[charPos].fWordStop)
    charPos++;
// skip whole characters until we hit a word-break/more whitespace
for( ; charPos < uspCache->length_CRLF; charPos++)
{
    if(logAttr[charPos].fWordStop || logAttr[charPos].fWhiteSpace)
        break;
}
// skip trailing whitespace
while(charPos < uspCache->length_CRLF && logAttr[charPos].fWhiteSpace)
    charPos++;

Remember that all we are doing so far is altering the logical cursor-offset for the TextView. The actual text-caret is positioned independently and does not require any form of sophisticated processing on our part. This is the great thing about Uniscribe - as a programmer we only have to deal with logical character units - all of the complicated display-related code is handled automatically for us.

Line Wrapping

I was hoping that this stage would not be necessary due to the linear (offset-based) coordinate system that we are using for Neatpad. However because of the way Neatpad handles CR/LF sequences, specific checks must be in place to detect when the cursor moves past the beginning/end of a line. Should this occur the cursor is moved onto the previous/next line accordingly.

The difficulty occurs because the text-caret should not be allowed to move past the CR/LF at the end of each line. In effect the CR/LF sequences are 'dead' characters that cannot be used as character-stop positions. The image below illustrates this by showing the caret at the very last position it can reach, despite there being a line-feed character at the end.

The task of wrapping to the previous/next line is deferred to the MoveLineEnd and MoveLineStart functions. You will therefore see the following code in many of the MoveXxxxPrev functions:

?
if(charPos < 0)
{
    charPos  = 0;
    if(m_nCurrentLine > 0)
    {
        MoveLineEnd(m_nCurrentLine-1);
        return;
    }
}

...and the following code in the corresponding MoveXxxxNext functions:

?
if(charPos == uspCache->length_CRLF)
{
    if(m_nCurrentLine + 1 < m_nLineCount)
        MoveLineStart(m_nCurrentLine+1);
    return;
}

I was hoping to use the SCRIPT_LOGATTR arrays somehow to 'skip' CR/LF sequences rather than have specific code just for this purpose. I realise that I have probably not found the neatest way to deal with line-wrapping but I've been working at this for so long now I'm just going to release what I've got. I anyone can suggest a nice way to deal with line-wrapping that doesn't require any additional processing then please get in touch...

Line navigation and Anchoring

Conceptually line-based navigation is very simple - on the surface all that is required is for the cursor's line-number to be adjusted - in order for the cursor to move up/down a specified number of lines. Unfortunately the implementation is slightly more complicated than that because of the necessity to support variable-width fonts.

The problem occurs when the user moves the cursor (or text-caret) up or down a line. The user's expectation when he/she hits the up/down arrows is for the cursor to be shifted vertically to the previous/next line. For fixed-width fonts this is not an issue - the caret's y-position can be adjusted quite freely and this is all that is usually required. However variable-width fonts require that the caret's x-position be potentially modified to ensure that the caret always locates to a valid character position. In effect the caret must always 'snap' to the nearest character-stop boundary when moving up or down.

The image above illustrates this idea. As the cursor moves down through the file, you can see various numbered caret-positions being displaced horizontally around a fixed vertical line. This vertical line brings us onto the next concept to explore which is sometimes referred to as 'anchoring'.

Quite simply, anchoring is the process where the text-cursor is kept as close as possible to a specific horizontal coordinate within each line when moving up or down through a file. Imagine that the user places the text-caret at a character-position using the mouse. This location is represented by an x-coordinate within the current line and also the line-number itself. When the user moves up or down the file using the arrow keys, they expect the cursor to follow the vertical line that they chose as closely as possible. In Neatpad's TextView I refer to this process as anchoring.

The anchor position is represented by the m_nAnchorPosX variable. It is set whenever the user moves left/right along a line, or instead places the caret using the mouse. Importantly, the anchor position is not set when the up/down arrow keys are used as this would defeat the object of the exercise.

?
UspXToOffset(uspData, m_nAnchorPosX, &charPos, &trailing, 0);

Now when moving to the previous/next lines, the appropriate character-position can be identified by calling Uniscribe's ScriptXToCP, which converts the anchoring-coordinate to a logical character offset. This function has been encapsulated by the UspXToOffset function within UspLib and can be seen above.

?
VOID TextView::MoveLineUp(int numLines)
{
    USPDATA          * uspData;
    ULONG lineOffset;
    int charPos;
    BOOL trailing;
    // move 'up' the specified number of lines
    m_nCurrentLine -= min(m_nCurrentLine, (unsigned)numLines);
    // get Uniscribe data for that line
    uspData = GetUspData(0, m_nCurrentLine, &lineOffset);
    // move to character position nearest the anchoring x-coordinate
    UspXToOffset(uspData, m_nAnchorPosX, &charPos, &trailing, 0);
    m_nCursorOffset = lineOffset + charPos + trailing;
}

The MoveLineUp function (above) shows how the cursor is adjusted when moving up through the file. The key detail here is the call to UspXToOffset. This UspLib function takes the caret-anchoring position and finds the closest Unicode character-offset.

Coming up in Part 17

Keyboard Navigation has the potential to be incredibly complicated due to Unicode. Fortunately the Uniscribe API solves all the complexity with the ScriptBreak API. This is another huge benefit of moving to Uniscribe - the amount of code that we have been saved from writing is quite significant.

Next up will be syntax-highlighting. I have decided that regular expressions are definitely the best method in this regard, however there are many issues that must first be solved before any further work is completed. Some of the forthcoming topics will probably be regular expressions, parsing techniques and finite-state machines (FSM). If you have any comments in this regard then I'd be happy to hear them!

‹ Integrating UspLib up Piece Chains ›

14 May 2006

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Piece Chains | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
piecechain.zip

Published on 22 Jan 2012

58.32 Kb

Download

Piece Chains

Design & Implementation of a Win32 Text Editor

OK so I lied about getting the syntax-highlighting implemented this time around. I got bogged down in "regular expression hell" and needed something else to concentrate on. So during the summer period I've instead been slowly implementing Neatpad's text-editing capability. As I hinted at in the very first installment of this tutorial series, I have decided to follow the "Piece Table" design. The aim of this tutorial will be to discuss the rationale behind this decision and also highlight some (but not all) of the implementation details. Note that I will not be discussing issues related to the editing of Unicode text (this will come in the next tutorial). Instead we will look at the design of the piece-table from a low-level perspective, independent of any Unicode concerns.

Several years ago when I started writing HexEdit there was very little information I could find regarding editor design. Google was unheard of and AltaVista was the search-engine of choice. I had a 33k dial-up modem and the single resource I stumbled over at the time was a paper written by a University professor named Charles Crowley. His paper 'Data Structures for Text Sequences' and the 'Piece Table' approach he described heavily influenced the design of HexEdit, making it one of the slickest hex-editors available. I later documented HexEdit's piece-table design (or span-table, as I called it then) in my 'Memory Management for Large File Editors' article. Today there seems to be a little more information available regarding Piece-Tables - and certainly more evidence of people using this design in their projects:

  • AbiWord is an extremely impressive cross-platform, open-source word processing package. Some of the authors maintain blogs in which they have discussed aspects of AbiWord's design. Of particular note is the discussion regarding AbiWord's Piece Table design, in which the author details the transition to a 'red-black' binary tree representation of the piece-table.
  • WinAsm Studio is an IDE for assembly-language programming. Antonis Kyprianou (the author) is also writing a 'piece-table' based editor for inclusion within the IDE.
  • Tweak is a terminal-based Unix hex-editor. According to the website description tweak uses a binary tree data structure to represent edits to the file. The article is well worth a read and there is some very interesting information available there.
  • C++ Rope class looks to be quite interesting. I know almost nothing about it, other than what is described on that page. The description seems to indicate that the rope-class is using a piece-table implementation but I don't know this for sure.

Piece-tables are not a new development by any means and have been around for several years in one form or another:

  • Xerox PARC (Palo Alto Research Center) produced the Bravo text editor in the 1970s. Bravo was the work of Charles Simonyi and Butler Lampson, and used a "Piece Chain" to implement it's editing functionality. Unfortunately the Alto platform (on which Bravo ran) is now so old that information on the Bravo editor does not appear to be available any more. Maybe the Altogether project (an Alto emulator) will get off the ground and we can re-explore these old computer systems.
  • Project Oberon is an operating system, compiler and programming language. The classic computer-science book 'Project Oberon' is available in PDF format here and details the design of the Oberon platform. The book has a very interesting chapter on text-management, in which the piece-chain technique is described in great detail.
  • Mutable Text is a Modula-3 interface for editing text streams. MText implements a piece-table design using a balanced binary tree for efficiency. The only information I could gather on the MText interface was from Compaq's old research website. Because Compaq no longer exist (HP bought them) I had to use Google-Cache to find this information.

Obviously the piece-chain technique has been around for over 30 years now, with the first notable occurance with the Bravo editor. The surprising thing is that this technique is still quite rare even today.

The Perfect Text Editor

Of course, there is no such thing as the perfect text editor - otherwise we would all be using the same tool and I wouldn't be writing this article-series in a (likely futile) attempt to create my vision of a perfect editor.

Even if we ignore the overwhelming differences between editor's user-interface design, there is still a great diversity in editor implementation. What I mean by this, is that there is no single 'true' design that all text editors follow. Instead many distinct methods exist - such as the buffer-gap scheme, linked-lists of lines, and the somewhat rarer piece-chain techniques - all of which have been used with varying degrees of success over the years.

One of the reasons the piece-chain method is not very common is the complexity of it's implementation for text editors. It is not the actual piece-management that is difficult, but rather the maintanence of the editor's line-buffers that becomes troublesome. For this reason many editors choose not to implement piece-chains - and mature editors such as Vi and Emacs can be seen to be very successful without this design.

The fact that all of these techniques exist is a strong indication that there is no overwhelming 'best' design for text-editors. I don't know at this stage if I am making the right decision by implementing piece-tables for Neatpad. However I am determined to complete this project, and at the very least I will know one way or another, if the piece-table design is suitable for a plain-text editor.

Piece Chains with Linked Lists

Neatpad implements it's piece-chain data structure using a doubly-linked list, which closely follows the original design of HexEdit. Whilst other structures such as binary-trees could be used, a linked-list is preferred for it's simplicity. The original design for HexEdit maintained a Head and Tail pointer for representing the start and end of the piece-chain. Most people should be familiar with this concept; if you aren't then I would suggest reading up on this subject before going any further. Anyway, the classic doubly-linked list (which I used in HexEdit) is illustrated below:

The snippet below further illustrates how such a linked-list might be initialized:

?
// sequence constructor
sequence::sequence()
{
    head = NULL;
    tail = NULL;
}

The small problem with this design is the way the head and tail pointers are managed. Whenever a node is inserted at the front or back of the list, specific code is required to handle these 'special case' conditions because the head or tail pointers need updating to point to the new nodes. Link management is cumbersome and over-complicates sequence manipulations a great deal.

An alternative design is to maintain what are termed 'sentinel' nodes. In this model, two dummy nodes are introduced at the start and end of the list. Their contents are not defined (lengths will be zero), but their very presence means that every node in the list is guaranteed to have a valid neighbour. In other words, apart from the sentinels themselves, every node's next and previous link always point to a valid node. This essentially removes any 'special case' code for dealing with insertions or deletions at the start and end of the list.

Linked-list with sentinel nodes.

To further illustrate, an empty list contains just two nodes; the head and tail sentinels, which simply point to each other.

Empty list.

Supporting this linked-list design is very simple. Instead of initializing the head and tail pointers to NULL, two 'empty' nodes are created which link between each other. Any nodes that are subsequently added are inserted in-between the sentinels.

?
sequence::sequence()
{
    head = new span(0, 0, 0);
    tail = new span(0, 0, 0);
    head->next = tail;
    tail->prev = head;
}

It is surprising how much work this simple idea saves. Of course I can't claim credit for this method - I originally read about this trick in one of Michael Abrash's assembly-optimization books many years ago.

Spans and Pieces

The term piece-chain is nice and succinct, because one can straight away imagine the pieces of text being chained together within the data structure. However I use the term span in Neatpad to represent each piece of text in the chain, purely because this is how I did things in HexEdit. There is no difference between a piece and a span apart from the name - they both serve the same purpose.

?
// span - private to the sequence
struct span
{
    span   * next;
    span   * prev;
    size_w   offset;
    size_w   length;
    int buffer;
};

The piece-table is formed by chaining together the span objects through their next and prev links. The linked-list is maintained by a sequence C++ class, which encapsulates the entire data structure inside a single object. The insert, replace and erase functions are included to provide the editing interface:

?
// define the type of strings the sequence will hold
typedef wchar_t seqchar;
class sequence
{
public:
    bool insert  (size_w index, seqchar *buffer, size_w length);
    bool replace (size_w index, seqchar *buffer, size_w length);
    bool erase   (size_w index, size_w length);
    bool undo    ();
    bool redo    ();
    // other members snipped
private:
    span * head;  // pointers to list sentinels
    span * tail;
};

The central idea behind spans is that they provide a level of indirection to the underlying file contents. The individual nodes never store any text - they only refer to ranges of text stored in the original file, or to any range of text in the modify-buffer added due to text insertions. You can see from the definition above that a span has no means for storing text - instead the offset, length and buffer fields identify a range (or piece) of text in the original file.

The image below illustrates a typical piece-chain organisation. The 'original file' buffer is initialized with the text "The brown fox jumped over the lazy dog". The single word "ing" has been inserted into the sequence - with this text appended to the modify buffer. The spans in the piece-chain form the sequence "The jumping dog".

An important thing to note is that spans have no knowledge of their logical position in the sequence. They only know the physical location of the data they reference. This has the advantage that new spans can be inserted or deleted from the piece-chain with no effect on the other spans in the list. This is why inserting and deleting from a piece-table is so fast - assuming that you know where to insert.

The flexibility of the piece-table's is also it's biggest drawback. Because spans don't know their logical position there is no way to directly access a specific text-offset within the document. All accesses must go through the linked-list by starting at the head of the list. Any time we want to locate a specific character-offset we must iterate through each span in turn, summing their lengths in order to keep track of the current logical position:

?
span * sequence::spanfromindex (size_w index, size_w *spanindex = 0)
{
    span  *sptr;
    size_w curidx = 0;
    // scan the list looking for the span which holds the specified index
    for(sptr = head->next; sptr->next; sptr = sptr->next)
    {
        if(index >= curidx && index < curidx + sptr->length)
        {
            if(spanindex)
                *spanindex = spanidx;
            return sptr;
        }
        spanidx += sptr->length;
    }
    // insert at tail?
    if(sptr && index == curidx)
    {
        *spanindex = curidx;
        return sptr;
    }
    return 0;
}

The spanfromindex function above shows how the piece-chain is traversed from the start in order to locate the span. The lengths of each span are summed together until the correct node is found. The simplicity of a linked-list design is also it's biggest drawback - random access to a linked list is slow.

Unlimited Undo & Redo

In theory unlimited undo+redo is incredibly simple with piece-tables. In fact it is almost 'free'. This is one reason why this technique is so desirable - the big selling point for me is that the piece-chain is (in computer-science terms) a Persistent Data Structure. In essence this means that the piece-chain preserves older versions of itself even when modified. Most importantly these older versions are easily restorable whilst still maintaining the integrity of the data-structure.

It is important to remember that the underlying data in the file we are editing never changes - it is only the linked-list nodes (the spans) that change, in order to represent modifications to the file. Therefore there is no need to maintain separate data-buffers containing the document contents which have been modified. The memory-savings the piece-chain brings are significant enough to warrant the added complexity this method.

The key to Neatpad's implementation of undo/redo is the use of span-ranges. Quite simply, a span-range is an object which represents a contiguous range of spans within the sequence. Any time the piece-chain is manipulated, a span-range object is pushed onto the undo stack which represents the range of spans affected by the edit.

?
struct span_range
{
    span  * first;
    span  * last;
    bool boundary;
    size_w  sequence_length;
};

Each span-range is therefore used to represent a single modification to the sequence. The first and last fields point to the range of spans that encompass a particular edit operation. All spans in the range are linked together internally using their regular next and prev pointers. Now whilst a span-range can conceptually hold multiple spans all chained together, it can also reference a single span by pointing both first and last to the same span.

Notice how the sequence-length is stored inside each span-range. This is necessary because we don't want to re-calculate the sequence-length each time we undo or redo an action (doing so would be very slow for large piece-chains). By preserving the sequence-length prior to editing the sequence we can easily restore this value when we undo.

Span boundaries are a special form of span-range. Think of a span-boundary as the gap between two adjacent spans. A span-range is still used to represent this form of 'empty' range, with its boundary field set to 'true' to indicate what kind of range it is.

The image above illustrates both kinds of span-range. The range on the left is a 'regular' range, with boundary set to false. The range on the right is an 'empty' range, and the span-range object has it's boundary field set to true.

In actual fact span-ranges are used for more than just holding history - they are also used as auxiliary helper objects when manipulating spans during insert and erase operations. They are just a convenient way to store sections of the piece-chain as they are moved in and out of the sequence.

Inserting Data

Data insertion is by far the simplest to implement. There are two basic scenarios that we need to consider - inserting in the middle of a span, and inserting at a span boundary. As we look at these operations in more detail, remember that edits to the text-sequence are just modifications to the spans in the list. Note that in all of these examples, the text is shown to be contained within each span. This is for illustrative purposes only - because we know that in reality the spans do not hold any text, rather they refer to ranges of text in the original or modify buffers. In addition the head and tail sentinel nodes are also shown - as the grey blocks at either end of the list.

Inserting in the middle of a span is the first scenario. We start with the piece-chain in the following state:

The sequence holds the text "TheQuickBrown" and for whatever reason the linked-list holds three spans. Our first example will be to insert the string "xxxx" at index "6". This position in the sequence happens to fall in the middle of the second span.

Inserting in the middle of a span requires that the span be split into two separate pieces - which represent the data before and after the insertion point. A third span is linked in-between these two new pieces which will represent the actual inserted data. As expected the inserted data is appended to the 'modify buffer' which leaves the original file-buffer untouched.

Although we have conceptually split a span in half in order to represent an 'insertion', in reality we do not do this. If you study the piece-chain you can see that the span representing the word 'Quick' has been removed from the sequence, to be replaced with three new spans. However rather than deleting this span we instead preserve it, by holding it inside a span-range object. This span-range is then pushed onto our 'undo stack' in order to represent the range of spans that were modified by the insertion.

This strategy of span preservation is key in this implementation of a persistant data structure. Notice that after the insertion has taken place, no spans in the piece-chain reference the "Quick" span anymore. However "Quick" still maintains it's own links back into the main list. This is a very important detail, because when it comes to restoring the sequence (undoing this last action) we need to know where-abouts in the list the spans on the undo-stack should be re-inserted.

Shown below is the basic outline for how data is inserted for this first scenario, taken from the sequence::insert function:

?
// initialize a new 'undo'. It will be pushed onto the undo-stack.
span_range *oldspans = init_undo(index, length, action_insert);
span_range  newspans;
// preserve the span that we are inserting into
oldspans->append(sptr);
// new spans for before and after the insertion point
newspans.append(new span(sptr->offset, insoffset, sptr->buffer));
newspans.append(new span(modbuf_offset, length, modifybuffer_id));
newspans.append(new span(sptr->offset+insoffset, sptr->length-insoffset, sptr->buffer));
// insert the new pieces into the sequence!!
swap_spanrange(oldspans, &newspans);
sequence_length += length;

All sequence-modifications in Neatpad follow the same basic pattern:

  1. Spans to be removed from the sequence are stored inside an "oldspans" span-range.
  2. Spans to be introduced into the sequence are stored inside a "newspans" span-range.
  3. The "oldspans" are pushed onto the undo-stack.
  4. The two span-ranges are then swapped around.

Inserting at a span boundary is the next scenario to consider. In this example the two-letter string "yy" will be inserted at sequence-position "6" again, which now falls between "Qui" and the "xxxx" span we inserted previously.

This time a single span is inserted into the list, in-between the "Qui" and "xxxx" nodes. The undo-event which represents this action is a span-range holding the spans either side of the insertion-boundary. This 'span boundary' is distinguised by the '*' symbol. Notice how these spans again maintain their links back into the linked-list.

Another important detail to notice is how the undo-stack has grown with the new span-range pushed onto it. Hopefully you can see how multi-level undo would be implemented: Each time we edit the sequence we push another span-range onto the undo-stack. Each entry in this stack represents the range of spans affected by that particular edit operation. Every time we perform an "undo" a span-range is "popped" from the undo-stack and re-inserted back into the main linked-list. The spans that it replaces are removed, but still preserve their own links back into the list. Each span-range that is removed during an undo is pushed onto a redo stack.

The code for span-boundary-insertion is much simpler because only one span is inserted this time:

?
// initialize a new 'undo'
span_range *oldspans = init_undo(index, length, action_insert);
span_range  newspans;
// this is a 'boundary insertion'
oldspans->spanboundary(sptr->prev, sptr);
// single span for the inserted data
newspans.append(new span(modbuf_offset, length, modifybuffer_id));
// insert the new span into the sequence in place of the old ones
swap_spanrange(oldspans, &newspans);
sequence_length += length;

The code follows the same pattern as before: define the 'oldspans', collect together the 'newspans' and then swap them around. The only difference this time is how the 'oldspans' are defined. No spans were modified in the boundary-insertion case so the span-range represents a span-boundary by pointing to the spans either side of the boundary we inserted at.

Erasing Data

Erasing data from the sequence is rather more complicated. In fact, it is a lot more complicated. The problem is that deletions can potentially encompass several spans, and can start and stop mid-span as well. Here are the scenarios that must be catered for:

  • Erase starts at a span boundary.
  • Erase starts mid-way through a span (in which case the span must be split).
  • Erase stops at a span-node boundary.
  • Erase stops mid-way through a span (again the span should be split).

We must also consider the case where a deletion can encompass several spans. All four scenarios above must be taken into account in this case. There is also the case were a deletion is contained entirely within a single span. Again, boundary or mid-span conditions must be taken into account.

The mistake I made with HexEdit's piece-chain implementation was to handle all of these cases separately which resulted in hugely over-complicated code. What I should have done, and what I have done this time around, is have one 'general-case' that handles all scenarios:

The example above shows a deletion which encompasses several spans, and both starts and stops mid-way through a span as well.

Because the deletion affected every span in the list, the entire linked-list has been pushed onto the undo-stack in the form of a span-range, to be replaced with just two new spans that represent the data before and after the deleted range.

The code starts by checking to see if the deletion starts mid-way through a span. If it does, that span is added to the oldspans span-range, and a replacement span created which represents the data in the span just before the deletion-index. This 'split' span is added to the newspans range:

?
// does the deletion *start* mid-way through a span?
if(remoffset != 0)
{
    // split the span - keep the first "half"
    newspans.append(new span(sptr->offset, remoffset, sptr->buffer));
    // have we split a single span into two?
    // i.e. the deletion is completely within a single span
    if(remoffset + removelen < sptr->length)
    {
        // make a second span for the second half of the split
        newspans.append(new span(
            sptr->offset + remoffset + removelen,
            sptr->length - remoffset - removelen,
            sptr->buffer)
            );
    }
    removelen -= min(removelen, (sptr->length - remoffset));
    // archive the span we are going to delete
    oldspans.append(sptr);
    sptr = sptr->next;
}

Once this first scenario has been handled, a loop is used to process any further spans until we reach the end of the deletion-range. Every node that falls under the 'delete range' is appended to the oldspans container object. A special-case is used to handle the scenario when a delete stops mid-way through a span.

?
// we are now on a proper span boundary, so remove
// any further spans that the erase-range encompasses
while(removelen > 0 && sptr != tail)
{
    // will the entire span be removed?
    if(removelen < sptr->length)
    {
        // split the span, keeping the last "half"
        newspans.append(new span(
            sptr->offset + removelen,
            sptr->length - removelen,
            sptr->buffer)
            );
    }
    removelen -= min(removelen, sptr->length);
    // archive the span we are replacing
    oldspans.append(sptr);
    sptr = sptr->next;
}

The very last thing to do is swap out the 'oldspans' with the 'newspans' and update the sequence-length:

?
swap_spanrange(&oldspans, &newspans);
sequence_length -= length;

All of this code can be found in the sequence::erase member function, but is a lot more complicated than what I have shown here. In particular there are special-cases for 'optimized' deletes which almost doubles the amount of code required. I have no intention of detailing this process any further, just look at the sourcecode download of you are interested.

Replacing Data (overwriting)

Data-replacement is the most complex operation to implement, and should be thought of as a hybrid form of erase and insert, combined into a single function. All of the complexities of these first two operations must be taken into account when implementing sequence::replace. In addition the 'optimized replace' scenario - in which multiple, consecutive replaces are coalesced into a single operation - makes replace very complicated indeed. I am not going to go into any great detail - suffice to say, the sourcecode download contains everything you need to know.

When I originally wrote HexEdit's sequence class, yet another mistake I made was to write a separate routine for replacing data in the sequence. I took the already overcomplicated 'erase' function and duplicated the code into a new 'replace' function. I then merged in the code for inserting data which made it even more complicated. This made code-maintanence very difficult because of the code-duplication problems.

I didn't make the same mistake second time around. Neatpad's sequence::replace function is simply a wrapper around sequence::erase and sequence::insert. First of all erase is called to delete the range of data that will be replaced. This temporarily shortens the sequence. Next, insert is called with the 'replace' data which is inserted into the place where the delete occured, re-growing the sequence back to it's original length. Importantly, these two actions are grouped together so that they appear as a single undoable action.

?
bool sequence::replace(size_w index, seqchar *buffer, size_w length)
{
    group();
    erase(index, length);
    insert(index, buffer, length);
    ungroup();
}

Writing sequence::replace this way significantly simplified the sequence's implementation. It wasn't quite as straight-forward as I make out because of the problem of 'optimized replaces' - the sequence::erase function must have a small amount of logic to cater for 'regular erases' vs 'replace erases'. But in general I found this 'shortcut' far more preferable to what I had done before. Importantly, any time I have to change the erase or insert functions (because of a bug for example) I don't have to modify the replace function in parallel - it gets all changes for free because it's just a simple wrapper function.

Piece-chain demo application

Testing a piece-chain implementation is rather difficult because of all the subtle scenarios that can occur. I started off with a simple console application in which I would type "insert index text"' or "erase index length". It was rather cumbersome so I took the UspLib Demo application which I wrote for a previous tutorial, and came up with the "Piece Chain Demo":

I found this form of test-harness very useful as it gives instant feedback as to the state of the piece-chain after each operation. It also shows the contents of the Original File, Modify-Buffer, and also the Undo stacks. It's not so useful now that I have integrated the piece-chain into Neatpad, but at the very least it might be interesting to anyone who is attempting to learn more about piece-chains. Give it a whirl!

Linked Lists vs Binary Trees

There is some debate as to how a piece-table should be represented. The two main choices are either a linked-list, or some form of binary tree. For the time-being Neatpad uses a doubly-linked list to manage it's piece-table. This method has been proven to be effective in my HexEdit application, but it remains to be seen whether or not a linked-list is suitable for a text editor:

Linked lists are very simple to manipulate and traverse. However locating a specific character-position can be potentially very slow if there are a large number of nodes in the list, because the list must be traversed from it's start any time a particular node is required. Caching can solve most of the linked-list's speed problems, because the majority of operations on the sequence will be very localized (you can only type within a fixed viewport of the document at any one time). Caching the last-accessed span can yield large improvements here.

Binary trees on the other hand are the exact opposite. A balanced binary tree is great for searching but bad for inserting/modifying. Searching is always O(log N), so locating a specific character-position is always fairly fast, even for a sequence that holds many thousands of nodes. However manipulating a binary-tree is always fairly expensive compared to the linked-list. Any time a node is inserted into a binary-tree, the tree must be rebalanced, and all children of that node must be updated to reflect the change.

In both cases the complexity of the data-structure we choose (the number of spans it contains) is a function of the number of edits to the file and is not at all related to the size of the file or the number of lines it contains. This makes the piece-chain a great data structure because it scales very well, even for very large files. It is far preferable to a "linked list of lines" model, which does not scale well for large files. The piece-table is definitely the data structure of choice in my opinion.

If we were concerned purely with edits to the piece-table, I believe the linked-list is the superior choice. Should we need to use the data-structure as a way to perform other kind of searches (such as looking up line-numbers, or mapping line-numbers to character-offsets) then this might require a move to a binary-tree implementation.

C++ templates and VS2005

For the moment I have resisted the temptation to implement the sequence class using C++ templates. This is primarily due to the poor template support in VC6.0, but I also wanted to make the initial version 'clean' enough for non-C++ programmers to understand. Currently the sequence class uses a hard-coded 'seqchar' datatype as it's basic unit of text, which is currently a 16bit WCHAR type. It is simple to re-target the sequence to any other character type by simply changing the typedef statement at the top of sequence.h, so for the time-being templates would be a luxury rather than a necessity.

However I have recently started using VS2005 and now have the opportunity to take advantage of a much better C++ compiler. If I do migrate to VS2005 it will make it harder to compile Neatpad under VC6.0 because the projects will be incompatible. And if I change the sequence-class to a template implementation, Neatpad won't compile under VC6.0 at all. What do people think about this proposal? If I get no comments I'll go ahead and migrate to VS2005...

Conclusion

I have deliberately tried to keep the piece-chain implementation as simple as possible. I see no need at this stage to migrate to a binary-tree implementation for the piece-chain, although future tutorials may have to address this issue if performance becomes a problem. The important thing is, it doesn't really matter if we use a list or a tree - everything is encapsulated inside the sequence C++ class so changing to a binary-tree would have no real impact on the rest of the design.

Piece-tables are actually a very simple concept and are not as complicated as they may seem from this article. The complexities arise from the way the piece-chain is optimized each time an edit occurs. Consecutive inserts, erases and replaces are coalesced in such a way that the number of spans in the list is always at a minimum. This requires that individual span's be adjusted 'in place' rather than introducing new spans into the table each time an edit occurs. This also complicates the undo/redo support as well. Without this optimization piece-chains are very simple, but unfortunatey a 'real world' piece-chain must have these features if it is to be successful.

The next tutorial will focus on how I integrated the piece-chain design into Neatpad. There are a lot of issues to discuss, mainly due to the subtleties of editing Unicode text. I also need to implement proper 'memory management' and large-file support - this will be covered in a later tutorial as well.

‹ Keyboard Navigation up Unicode Text Editing ›

12 Sep 2006

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Unicode Text Editing | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
neatpad18.zip

Published on 22 Jan 2012

216.98 Kb

Download

Unicode Text Editing

Design & Implementation of a Win32 Text Editor

Unicode character input presents some unique problems for text-editors - issues that did not have to be considered when the first ASCII editors were written. The main difficulties are the Unicode 'combining sequences' - where multiple code-points are combined to form a single selectable 'character cluster'. Modifications to a Unicode text-file require careful coding to ensure that character cluster-boundaries are preserved and that no invalid sequences are inadvertantly introduced into the document. The Uniscribe API will again be used to aid us in this area.

Character input (of any kind) is not possible without some form of data-structure to manage and represent any alterations to the document. The last tutorial saw the implementation of a piece-table data structure which implements three basic edit operations: insert, erase, and replace. Unlimited undo and redo are also supported. The sequence class was presented which encapsulates the piece-table and these basic editing operations within a single C++ object. The purpose of this tutorial is therefore to document the modifications required by Neatpad to support the piece-table editing model.

Unicode character input

We already looked at keyboard navigation in Part 16 - Keyboard Navigation, in which we discussed caret movement within a Unicode document, and we briefly looked at the various Win32 character-input messages that a program can encounter when receiving keyboard input:

 | Characters | Dead Characters
UTF-16 Character | WM_CHAR | WM_DEADCHAR
UTF-32 Character | WM_UNICHAR |
Input Method Editor | WM_IME_CHAR |
System Character | WM_SYSCHAR | WM_DEADSYSCHAR

Even though the WM_CHAR message has been around since the first versions of Windows, it is still the most appropriate way for a Win32 application to receive character input. For any UNICODE application, the WM_CHAR message sends a single UTF-16 character value instead of a plain ANSI character. This is perfect for us, because Neatpad is already an UTF-16 (wide-character) application. Even complex scripts will be handled seemlessly because keyboard input for these languages is usually associated with an Input Method Editor (IME) - which will translate any 'complex' key-strokes into the appropriate stream of UTF-16 characters, without any extra work on our part. The Windows Input Method Editor will be the subject of a future tutorial.

The other messages look interesting but are not really necessary. Supposedly the WM_UNICHAR message sends UTF-32 characters rather than the 16bit WCHARs - however I have never seen WM_UNICHAR being sent to a program, even on a XP machine. I suspect that this is a message that is sent by other applications (such as IME's) rather than the OS itself. Likewise the WM_IME_CHAR message is only sent under special circumstances. Although we will ignore these additional input-messages, we will not be losing any functionality by simply handling WM_CHAR at this point.

The code below shows the standard method of handling character-input in a Win32 program:

?
LONG TextView::OnChar(UINT nChar, UINT nFlags)
{
    // do something with 'nChar'
    return 0;
}
?
case WM_CHAR:
    return OnChar(wParam, lParam);

We don't need to do anything special to receive Unicode input. As long as we compile with the UNICODE macro defined we will receive UTF-16 characters. Unicode values outside of the BMP (i.e. > 0xFFFF in value) will be sent as two separate messages, one for each surrogate character. However it is very unlikely that a user will manually enter two surrogate values separately - more than likely they will be using an Input Method Editor, and it will be the IME that breaks their keyboard input into UTF-16 units.

Regardless of how the text is entered, all we need to do is receive each WM_CHAR as it is sent to the TextView and process it accordingly. Usually that's where the tricky part comes in, but fortunately we now have the piece-chain sequence class which will enable us to make edits to the underlying document each time a character is received.

Integrating the Piece-table

My first attempt at integrating the piece-table into Neatpad went quite smoothly. I'm not completely certain it's the best way to do things in an editor but at least it shows that the idea can work in practice. The image below illustrates the various components of the text editor as it stands at the moment.

The basic idea has been to incorporate the piece-table in a manner that caused minimal impact on the rest of the design. To achieve this aim I layered the piece-table directly on top of the raw file content. The piece-table (or sequence class) therefore presents the underlying file in it's raw form - as a sequence of BYTEs rather than WCHAR units. This makes sense for the moment, because the TextDocument can still accesses the file in it's raw form prior to converting it to UTF-16. The only difference now is that the TextDocument can affect changes to the raw file through the piece-table as well as read it's content.

Not everything is as smooth as it seems however. The big problem with this design is the reliance on the line-buffer, which must be reinitialized every time a change is made to the piece-table. This is because the line-buffer indexes directly into the piece-table, and any change to the piece-table (such as an insert or delete) effects the layout of the file. Therefore any change to the piece-table will require the line-buffer to be modified appropriately. This is not really noticable on very small files, but any file larger than a few kb suffers serious performance problems. You will notice this problem with the text-editor download this time - any time a single character is inserted there will be a noticable delay as the line-buffer is reinitialized. Of course this is unacceptable and this issue will be addressed fully in the next tutorial.

Deleting text

Even though text-input is the primary function of a text-editor, text-deletion is an important aspect too. So before we look at how text is entered I will describe some of the issues involving text deletion.

There are three basic forms of 'delete' that a Unicode text-editor must consider. The first is the deletion of a staticrange of text - usually defined as the user's current selection. The key detail here is that the starting and ending positions of the deletion range are well defined, and we rely on the existing keyboard and mouse navigation routines to always place the cursor at proper cluster boundaries. The other two delete methods are Forwards delete and Backwards delete. These last two operations typically map to the standard <delete> and <backspace> keys. We will discuss these last two forms of deletion a little further down.

Text-deletion in Neatpad is made slightly more complicated because of the way Neatpad currently manages the underlying data in the document. Neatpad is basically a multi-format text-editor which supports ASCII, UTF-8 and UTF-16 formats. So even though all text coordinates are in UTF-16 units (in the TextView), the TextDocument must map these coordinates to whatever format the document is in. (This design was discussed in Part 9 - Unicode Text Processing). Deleting a range of text in the document requires Neatpad to convert the character-offsets into the appropriate physical offsets in the underlying file.

The TextDocument performs this conversion by accessing the Line-Buffer component of the editor. Although lookups through the line-buffer occur in O(log N) time, this is a relatively costly operation for larger files. This presents a problem, because our delete operation takes as input two values - the start and end of the range of text to delete - and requires two lookups through the line-buffer. It's not ideal but this is just the way the editor has evolved up to this point. Remember that the whole reason these conversions to/from UTF-16 are necessary is because I want Neatpad to support multiple file-formats. UTF-8 is the real killer here because it doesn't correspond at all to UTF-16, making the conversions an absolute necessity. It is quite likely that I will move away from this 'dual coordinate' design!

In an ideal world we could load a UTF-8 file entirely into memory, converting it to UTF-16 when it is first opened so that there would be no conversion issues at runtime. This is how most editors work. It is only because of my intention to support 'large files' that we have these issues. Obviously a multi-gigabyte text-file will not fit into memory all at once and will require us to page the file into memory in smaller units, making the runtime conversions to/from UTF-16 a requirement. But I really don't like the 'dual coordinate' system that I have at the moment (it's very messy to deal with), and because of this I may decide to limit 'large-file' support (on-disk editing) to just plain ASCII and UTF-16. As always I'd appreciate any feedback people may have to offer.

Backspace vs Delete

This is the title of one of Michael Kaplan's Sorting it all out blog entries. You should take the time to read Michael's blog as there is a wealth of high-quality information regarding Internationalization. Many of the topics on his site have been tremendously helpful when it came to understanding Unicode.

Although delete and backspace are both methods of text-deletion, their operations are subtly different in practice. In fact a better way to describe their actions are Forwards delete and Backwards delete, as these are the directions (in logical text-units) that the cursor moves in each case. The differences can be highlighted by examining a string of Unicode text containing combining sequences. As an example we will consider the word "Déja":

As you can see the text is made up of five code-points - with the combining accute accent (U+0301) shown as a separate character. In practice of course, the accute-accent is positioned above the letter 'e' and together they are rendered as a single grapheme cluster:

Fowards-delete is the first case to consider, and a basic text-editor would implement this operation by removing a single Unicode character each time the <delete> key was pressed. However if we look at the 'Déja' example we will see it is a little more complicated than that:

Imagine that the cursor is positioned at the start of the letter 'e'. If we hit <delete> then this single letter would be erased as expected. However if Neatpad just deletes a single UTF-16 character each time delete is pressed, the combining accute-accent character would not be removed. In fact it would become attached to the letter preceeding the 'e' (the letter 'D'), resulting in the illegal Unicode sequence shown above.

Of course the logical thing to do in these scenarios is to delete the base character and any combining characters that might follow. The same behaviour would also be used for UTF-16 surrogate pairs so that both surrogate-characters are deleted. The Uniscribe 'logical attribute list' is used to identify these character boundary conditions.

Back-delete is slightly different in practice. Imagine this time that the caret has been placed in-between the accute accent and the letter 'j'. Performing a back-delete using the same logic as before would remove the combining character as well as the base character. Doing so is not incorrect, but a nicer alternative is to remove just the combining character, leaving the 'e' untouched:

Deleting single Unicode characters in response to a <backspace> is preferable because only the combining-character is removed from the end of the sequence, leaving the base character intact. Deleting the entire sequence is not necessary and would only cause the user frustration if they had only mistyped this one character. Unfortunately blindly peforming a single-character delete is not sufficient because we must still consider UTF-16 surrogate-pairs - which must be treated (and deleted) as an atomic unit of text. The blog post I referred to earlier discusses this scenario and highlights a bug in several Windows applications which don't handle surrogate-pairs properly in this case.

Supporting Unicode delete operations requires us to inspect the text-stream we are modifying and handle any combining sequences (including surrogrates) appropriately. Fortunately because we are using Uniscribe we can again refer to the logical-attribute list which is calculated for each line - remember that this list identifies cluster boundaries and enables us to easily detect combining sequences.

Line-termination is another scenario that must be delt with in addition to the surrogate / combining-sequence issue. It's not really a problem in itself, other than special care must be taken when carriage-return/line-feed combinations are encountered. Obviously CR/LF pairs must be treated as a single unit - but really they are handled in the exact same way as surrogate-pairs.

Because Neatpad's piece-table is not a line-oriented data structure there are no further issues to deal with. In contrast, an editor that utilized a 'linked list of lines' model would require special logic to manipulate the list-nodes whenever lines (CR/LFs) are added/removed from the file. Neatpad doesn't have this problem as it's underlying data structure is a simple flat stream of characters.

Inserting text

Neatpad receives character input via the TextView::OnChar function. This function is defined in TextViewKeyInput.cpp and is shown below:

?
LONG TextView::OnChar(UINT nChar, UINT nFlags)
{
    WCHAR ch = (WCHAR)nChar;
    // translate carriage-returns to CR/LF combinations
    if(nChar == '\r')
        PostMessage(m_hWnd, WM_CHAR, '\n', 1);
    // input this single character at current cursor position
    EnterText(&ch, 1);
    // 'break' piece-table optimizations whenever we input a new line
    if(nChar == '\n')
        m_pTextDoc->m_seq.breakopt();
    NotifyParent(TVN_CHANGED);
    return 0;
}

TextView::OnChar simply passes each wide-character it receives to the more general-purpose EnterText function, which is also used by the clipboard-related code:

?
BOOL TextView::EnterText(TCHAR *szText, ULONG nLength)
{
    ULONG selstart = min(m_nSelectionStart, m_nSelectionEnd);
    ULONG selend   = max(m_nSelectionStart, m_nSelectionEnd);
    BOOL fReplaceSelection = (selstart == selend) ? FALSE : TRUE;
    switch(m_nEditMode)
    {
    case MODE_READONLY:
        return 0;
    case MODE_INSERT:
        // remove selection if necessary
        if(fReplaceSelection)
        {
            m_pTextDoc->m_seq.group();
            m_pTextDoc->erase_text(selstart, selend-selstart);
            m_nCursorOffset = selstart;
        }
        // enter the text!
        m_pTextDoc->insert_text(m_nCursorOffset, szText, nLength);
        break;
    case MODE_OVERWRITE:
        // overwrite happens here
        break;
    }
    m_nCursorOffset += nLength;
    return TRUE;
}

It is the responsibility of EnterText to take the appropriate action depending on the state of the current edit-mode - which can be either MODE_READONLY, MODE_INSERT or MODE_OVERWRITE. Only 'text insert' is shown above - the overwrite is described later on but is much the same in terms of implementation.

?
ULONG TextDocument::insert_text(ULONG offset_chars, WCHAR *text, ULONG length)
{
    ULONG offset_bytes;
    offset_bytes = charoffset_to_byteoffset(offset_chars);
    return insert_raw(offset_bytes, text, length);
}

The key detail to highlight at this stage is what the TextDocument::insert_text function does with the WCHAR buffer that is passed to it. The first thing it does is to convert the supplied character-offset into a raw-byte offset. Once insert_text knows where to physically insert the text into the piece-table, the insert_raw function is called - which then converts the UTF-16 string into the appropriate text-format (or leaves it unchanged if the file is already UTF-16). With the text now in the same format as the underlying file's, it can finally be inserted into the piece-table.

Overwriting text

Whilst all text-editors support an 'insert' mode, a large proportion also provide an 'overwrite' mode as well. Neatpad's piece-table implementation already provides a 'replace' function - which combines erase+insert into a single atomic operation. The prototype is shown below:

?
size_w sequence::replace(size_w index, seqchar *buffer, size_w length)

The piece-table's replace function works by first erasing the specified range of text (between index and index+length), and then inserting the data in buffer back into the same location. The result is a 'strict' overwrite of binary data, with the file's length unchanged at the end of the operation. This behaviour is a hang-over from HexEdit's original design.

Overwriting text in Unicode is more complicated than simple byte (or character) replacement, as I found out when I finally started editing files with Neatpad. Text-overwrite must follow the exact same rules that we already discussed with 'forward deleting'. That is, we must respect cluster boundaries and combining sequences when we are replacing text. Consider the following example, in which the letter 'o' is entered whilst in 'overwrite' mode.

Ultimately this results in a scenario where we may need to delete several UTF-16 characters for every single character we type in at the keyboard. In other words, a 'replace' operation in a Unicode text editor could very well change the length of the file that it is editing. This is in stark contrast to a binary editor which must never change the length of any file it is editing. Unfortunately that's just the way text-editors work and we must be prepared to deal with these eventualities.

Replacing past the end of line

Replacing (overwriting) past the end-of-line is another scenario that must be catered for. Once again it wasn't until I was actually using the editor that I realised this was an issue at all. Consider the following two lines of text as a simple example, with the caret positioned at the end of the first line:

Although the editor has split the text across two lines, remember that Neatpad's piece-table presents the file contents as a simple stream of text:

Using the existing forward-delete logic in 'overwrite mode', the CR+LF combination would be deleted when the next character is input. Erasing the line-terminater in this way would have the effect of joining the two lines together:

Obviously this is not the behaviour we expect for a text editor. Neatpad must therefore give CR/LF characters special treatment whilst in overwrite mode. In fact, any entered text that immediately preceedes a CR/LF must actually be inserted into the document (extending the line length), regardless of whether the editor is in overwrite mode or not.

The CR/LF scenario highlights the same deficiency in the sequence::replace function that we first saw when overwriting combining-sequences. The underlying problem is that the number of bytes (or characters) we want to delete does not have to equal the number of characters we are replacing them with. We therefore need to be able to exactly control the number of bytes to delete for any given replace operation:

?
sequence::replace( size_w           index,
                   const seqchar *  buffer,
                   size_w           length,
                   size_w           erase_length
  );

Adding the erase_length parameter now gives us the flexibility to handle any form of replace operation. When replacing over combining sequences this parameter would be set to the number of bytes that the combining sequence occupies in the file. For the CR/LF scenario above, this parameter would be set to zero.

Optimized insert and replace

You may be wondering why we couldn't just have called sequence::insert rather than sequence::replace for the example above. The reason is simple: Neatpad's piece-table is designed to optimize each insert/replace operation, by coalescing consecutive edits (of the same type) into a single operation - in order to keep the number of spans in the piece-table to a minimum at all times. If we started off calling sequence::replace when entering text, but switched to calling sequence::insert when dealing with CR/LFs, this would break the optimized operation into a 'replace' and an 'insert' operation. This is not an intuitive thing for the user to understand - they would expect their overwritten text to be treated as a single operation. Rather than complicate matters I simply decided to extend the capability of the sequence::replace function.

Whilst we are on the subject of CR/LF combinations and sequence-optimizations, it is probably worth mentioning what happens when the user actually enters a CR/LF into the document (rather than deleting it). Neatpad's piece-table would naturally take the CR/LF characters and coalesce the edit-operation where possible. If the user entered several lines of text consecutively in one go (without undoing/deleting/redoing anything), all lines of text would be optimized into one operation. The consequences of this optimization would become noticable when the user subsequently decided to undo the text-insertions - when they would find that all the lines of text they entered would be undone in one go.

It turns out that this is not actually very intuitive. Instead what is preferable is for the sequence-optimizations to be 'broken' whenever a new line is started - in other words the sequence should only optimize whole lines of text and nothing more. To support this model a new function sequence:: breakopt was introduced:

?
void sequence::breakopt()

This function is called whenever a single carriage-return is entered into the document. The result is more natural because editors are line-oriented by nature. Breaking the sequence-optimization in this way allows each line to be undone/redone individually. This does result in more spans being introduced into the piece-table - but there will always be a trade off between usability and performance. In my opinion it is better to improve the user's overall experience rather than forcing optimization behaviours on them that they don't understand, and don't even care about.

Undo and Redo

The ability to perform undo and redo has always been a fundamental aspect for many text editors. Some do it better than others - with the best editors usually having undo support designed in from the very start. The piece-table implementation developed for Neatpad provides unlimited undo and redo - that is, any successful modification to the text-sequence is guaranteed to be undoable. Whilst this is a very nice feature in itself, an interesting issue is raised when the piece-table is integrated into Neatpad.

The issue is that of selection-highlighting and caret placement. You may or may not have noticed, but when you perform an undo (or redo) in your favourite editor, the text-caret is automatically repositioned to the offset where the last action occured. This means that for every action taken on the text-sequence, the piece-table must keep track of the logical offsets that were specified in those operations. When the sequence is restored due to an undo, the sequence must be able to provide details of what range of data in the sequenece was modified as a result of the undo/redo.

As a quick example, load up your favourite editor and open a file. Select a range of text and delete it, then reposition the cursor somewhere else in the file. Now hit 'undo' in the editor. The range of text that you just deleted will be inserted back into the file and will reselected as it was just before the edit took place. To achieve this goal, the sequence class stores the offset and length of each operation in the corresponding span_range object. The sequence class provides two methods which provide access to these internal details:

?
size_w  sequence::event_index();size_w  sequence::event_length();

The TextView will call these two methods any time an undo or redo occurs in the editor. The event_index() function provides the logical offset of the last operation, whilst event_length() provides the length of data involved. Any time an undo/redo occurs, the internal state of the sequence will change, and these two functions will return the appropriate values that represent these actions. Whilst the only reason this information is needed is for the caret can be repositioned during undo/redos, it is still an important aspect of the user interface.

Caret Placement

It has been difficult to test Neatpad's keyboard navigation behaviour because up until this point it was not possible to enter any text into the editor. Well it turns out there was a bug in the way the caret was being positioned in bidirectional text. I was never quite sure I had the logic right, and using Neatpad to edit mixed English / Arabic text confirmed this.

The Uniscribe topic Displaying the Caret in Bidirectional Strings (in MSDN) describes this problem by saying that "in bidirectional text, the caret position between runs of opposing direction is ambiguous." What this means is that for a string that contains both English and Arabic text, for certain character offsets in the string the caret can appear in more than one visible position.

Let's look at the familar "HelloيُساوِيWorld" example again, with the caret positioned at offset 5 in the string:

You will see from the example above that there are two text-caret positions. This ambiguity arises because the last letter of 'Hello' immediately precedes the first letter of 'يُساوِي'. So because the Arabic string is printed right-to-left there are two visible positions for the caret, depending on whether it is considered to follow the 'o' of 'hello' or to precede the first letter of 'يُساوِي'.

In Unicode text one can therefore think of the caret being displayed at the leading or trailing edge of a character. Another way to think of this is that the caret position is dependent on whether the caret is advancing forwards or backwards through the text. Any text editor supporting bidirectional text must therefore make this distinction as it is positioning the caret. It is not enough to simply convert the cursor offset to a visible x-coordinate. The direction the cursor was moving must also be taken into account:

?
if( fAdvancing )
    ScriptCPtoX(nCursorOffset - 1, TRUE, ..., &iCaretX);
else
    ScriptCPtoX(nCursorOffset,     FALSE, ..., &iCaretX);

The logic is actually quite simple: if the caret is advancing forwards, it is positioned at the leading edge of the preceding character, whereas if the caret is moving backwards it is positoned on the trailing edge of the current character. This new logic is represented in Neatpad with a change to the UpdateCaretOffset function, which is used internally by the TextView:

?
VOID TextView::UpdateCaretOffset( ULONG nCursorOffset,
                                  BOOL fAdvancing,
                                  int * outx,
                                  ULONG * outlineno )

The fAdvancing parameter is now required in addition to the character-offset. UpdateCaretOffset applies the logic shown above to position the caret correctly for bidirectional strings. It wasn't very difficult to adjust the keyboard navigation code either - the only change necessary was to set the fAdvancing variable according to the direction the cursor is moving. Keys such as VK_LEFT and VK_BACKSPACE are considered to move backward through the text, so fAdvancing is set to FALSE. For all other keys fAdvancing is set to TRUE. For example:

?
case VK_LEFT:
    fAdvancing = FALSE;
    nCursorOffset --;
    break;
case VK_RIGHT:
    fAdvancing = TRUE;
    nCursorOffset ++;
    break;

Vista is here

Like it or not Vista's arrival is imminent, and whilst I haven't quite made up my mind whether I'm a fan myself, Neatpad does require a couple of modifications in order to make it work correctly under this new operating system. The issue revolves around Administrator privileges and Vista's new User Account Control prompt. The problem occurs when Neatpad's Options dialog is invoked - because a couple of the settings ("Add Neatpad to Explorer context menu" and "Replace Notepad as default editor") require write-access to the HKEY_LOCAL_MACHINE branch in the registry. Obviously this requires Administrator privileges, and Vista's solution is to require applications to conform to the new User Account Control guidelines.

The first noticable change is the new Vista Shield Icon - which indicates to the user that elevation to Administrator via the UAC prompt will be necessary. There are three mechanisms available to application programmers which invoke the UAC prompt:

  1. Embed a UAC manifest (an XML file) into an executable's resources that indicates which elevation level is required. The choices for the requestedPrivileges section are 'asInvoker', 'requireAdministrator' and 'highestAvailable'. Whilst we could employ this method to 'requireAdministrator', the problem is that the UAC prompt would be displayed every time Neatpad was run. This would be very frustrating to the user, especially as the only time Administrator access is required is when the two 'system settings' need to be changed. This is a sledge-hammer approach that is only intended to support older applications and is not suitable for a simple utility such as Neatpad.
  2. The second option is to use the new CoCreateInstanceAsAdmin function call. This new COM API allows a program to instantiate a COM object at Administrator level (assuming that the current user is a member of the Administrator's group of course!). The COM object lives inside a separate process and allows the calling application to make controlled calls into the object. Whilst this is a technically neat way of doing things there are two problems. Firstly, we require an external DLL to be shipped, and secondly we have to be Administrator in order to install the COM DLL in the registry in the first place. This method is best suited for larger applications that use a proper Windows installer.
  3. The final option is to request a separate process be spawned at Administrator level. The ShellExecuteEx function can be called in this case, using the "runas " verb. When running under Vista, this will cause the UAC prompt to be displayed prior to launching the new executable. The child process will run with Administrator privileges, assuming that the user authorised the elevation.

Neatpad uses option#3 but instead of launching a separate program, it simply attempts to re-spawn itself. A special command-line option ("-uac") is used to instruct the new instance not to display it's GUI. In this special mode, Neatpad sets the appropriate registry-keys under HKEY_LOCAL_MACHINE and exits. The registry access succeeds because the respawned Neatpad will be running as Administrator.

Conclusion

Unicode text-editing is a complicated subject, mostly due to the occurance of combining sequences. Fortunately the Uniscribe API vastly simplifies the amount of work we might otherwise have to do - I certainly wouldn't look forward to writing an editor without this kind of language support. The topic this time was also helped a great deal by having a solid piece-table implementation which plugged in very neatly to the editor design. I strongly advise anyone writing an editor to invest the time in developing a functional back-end - which should include all of the necessary edit operations as well as undo/redo support.

There is still a long way to go before Neatpad's editing is fully complete. The line-buffer implementation needs an urgent overhall and this will be the topic of the next tutorial. Following on from this will be memory-management for large-file editing, which should hopefully be a very simple topic because the piece-table lends itself very well to on-disk editing.

Neatpad running under Windows Vista, using the Aero Glass theme.

I wrote last time that I was thinking of migrating to Visual Studio 2005 and C++ templates. Thankyou first of all to everyone who provided feedback on this topic - however at this point I decided to stick with VC6 for the time being. Firstly, C++ templates are unnecessary because the sequence class is only being used to store raw bytes. And secondly, VC2005 executables introduce a dependency on a new C-runtime DLL (MSVCRT8.DLL) which is not present on Windows systems by default. So rather than complicate matters the Neatpad project will remain VC6 compatible - but it will still build cleanly under VC2005 as well.

In the meantime I've come across a few interesting editors which are worth a mention. The first is JujuEdit from Jujusoft. It's freeware but closed-source. The editor has 'very large file support' - files up to 2Gb - and is actually very impressive in it's large-file handling. Next is Intype which looks to be an interesting editor still in development, and also e - the collaborative text editor for Windows, which has an interesting approach to undo/redo. Thanks to Franck Marcia for providing these last two links.

Thanks also to everyone who pointed me at Colorer - a very impressive open-source library for regular-expression-based syntax colouring. When the time comes to implement syntax-colouring in Neatpad this library will hopefully make things much easier.

‹ Piece Chains up

28 Nov 2006

Posted in Tutorials

editor
win32

Design & Implementation of a Win32 Text Editor

  • Neatpad Overview
  • Loading a text file
  • Scrollbars & Scrolling
  • Enhanced Drawing & Painting
  • Mouse Selection & Highlighting
  • Scrolling with the Mouse
  • Margins and Long Lines
  • Introduction to Unicode
  • Unicode Text Processing
  • Transparent Text
  • Introduction to Uniscribe
  • Uniscribe Mysteries
  • More Uniscribe Mysteries
  • Drawing styled text with Uniscribe
  • Integrating UspLib
  • Keyboard Navigation
  • Piece Chains
  • Unicode Text Editing
Powered by Drupal


Introduction to Device Drivers | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact
drvload.zip

Published on 22 Jan 2012

9.54 Kb

Download
hello_drv.zip

Published on 22 Jan 2012

1.39 Kb

Introduction to Device Drivers

Kernel 101 - writing a simple Windows NT device driver

Introduction to Device Drivers

Seeing as I've been spending alot of time doing kernel-level programming recently I thought it was time I wrote a few articles on the subject. This first article (and the rest in this "kernel" series) will cover the steps needed to build, install and start a Windows NT device-driver. Now, although the term "device driver" may seem intimidating to anyone who has never written such software, in reality this term is really a generic label for any software module which acts as a core operating system service. By following this tutorial you will see how simple it is to create your own device driver from scratch.

There are many confusing technologies associated with Windows kernel-programming. Legacy NT4-style device drivers, WDM (Windows Driver Model) device drivers, filesystem filter drivers, bus-drivers, miniport-drivers - the list goes on and on. Don't be mislead by all these terms though. All types of device-driver share the same underlying image format - the Microsoft Portable Executable. The only real difference is the libraries and header files used to build the drivers - and once built, the drivers all work in very similar ways.

At the very heart of a kernel device-driver is a single file very similar in concept to a DLL - called a SYS file. The only difference between a driver and a DLL is what libraries the driver links against - Windows NT drivers (SYS files) link against ntoskrnl.exe and HAL.DLL, Win32 PEs (DLL/EXE) link against kernel32.dll and ntdll.dll.

This tutorial will show you how to create an NT4-style "legacy" driver. This type of driver will not only function perfectly well on Windows 2000 and XP - but will also work on Windows NT4. Because we don't need the features that newer driver-models support we can make our lives much simpler by using this older style of driver.

Getting Started

Writing a device-driver is very simple because Microsoft gives you all the tools you need to get started. And you don't need to spend lots of money or use complicated IDEs, because the official Windows Device Driver Development Kit (Windows DDK) can be obtained from the following location:

http://www.microsoft.com/whdc/devtools/ddk/default.mspx

You only need to pay for the shipping cost of the CDs - and if you have an MSDN subscription you will have the DDK CDs anyway. If you want to target other Windows OS's other than XP then don't worry: the XP-DDK can be used to build "older" drivers, as long as you don't use newer kernel functions that only exist in XP.

Once the DDK is installed simply use the Start menu to open up the command-prompt build environment:

Start Menu -> Windows DDK -> Windows XP Checked Build Environment

Everything can done via the command-line with Windows Drivers. The command-line build environment (shown above) helps you out because all the correct environment-variables are already set, although the only one that is really necessary is "BASEDIR" which must point to the DDK install directory.

All your source-editing can be done using your favourite text-editor (e.g. Visual Studio is generally what people use). However it is quite difficult at first to configure Visual Studio to compile a driver project because Visual-Studio is designed to use the Platform SDK and the Visual C++ compiler. The DDK comes with its own compiler which must be used instead of the Visual Studio compiler (this is all that Microsoft supports - obviously with some trickery you could use the normal compiler, but that's not the point). So to start with we will use the DDK build environment and not Visual Studio. Maybe a later tutorial can address using Visual Studio for driver projects.

One other thing I should mention is the OSR (Open System Resources) website, www.osronline.com. This is by far the best resource on the Internet for device-driver development, so head over there and subscribe to their forums.

Hello World, Kernel Style

Writing a device-driver in Windows is incredibly simple - but first create an empty directory to store your driver project in. Use a simple path with no spaces in it, just because it is easier to navigate to in the command-prompt:

C:\DRIVERS\HELLO\

Create an empty text file in this directory and call it "hello.c", then type the following code into it:

?
#include <ntddk.h>
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    DbgPrint("Hello, World\n");
    return STATUS_SUCCESS;
}

Pretty simple, right? The DriverEntry is a function required by the DDK - it is similar in concept to DllMain because it is called when your driver first loads - however your driver remains loaded after you return back to the kernel.

The two parameters are worth mentioning. DriverObject is a pointer to a DRIVER_OBJECT structure, a kernel data structure used to represent the loaded device driver. RegistryPath is a unicode string which holds the corresponding service entry location in the registry.

Unlike win32 programs which use BOOL return types, and TRUE and FALSE, kernel drivers use NTSTATUS return types. This is a 32bit value which can hold many different error codes and severity levels. The value STATUS_SUCCESS (which is zero) is used to indicate a successful return value. Other STATUS_xxx codes can be found in the ntstatus.h header file. Note that, if we return anything other than STATUS_SUCCESS from DriverEntry, our driver will fail to load - so make sure you get it right!

Building a Driver

Assuming that you have created your project directory, you need to create two more files which the DDK build utility requires in order to build your project. The first file you need to create is called "makefile" - note that there is no extension on this filename. Be very careful about this last point - if you have "Hide Extensions for known file types" enabled in your Windows settings then Notepad will automatically append a ".txt" extension onto your files without you realising.

?
!INCLUDE $(NTMAKEENV)\makefile.def

makefile should contain a single line (shown above) and must never be edited in any way. Usually the DDK samples include such a file, so you can copy it straight from there.

The second file you need to create is your main project file, called "sources", again with no file extension. The contents of this file direct the DDK build utility to your sourcecode files.

?
TARGETNAME = hello
TARGETPATH = obj
TARGETTYPE = DRIVER
INCLUDES   = %BUILD%\inc
LIBS       = %BUILD%\lib
SOURCES    = hello.c

sources contains two important pieces of information - the name of your driver, and a list of source-files. If you have more than one source-file, then they should be listed one-after-the-other on the same line, separated by a space. You have to be careful with filenames that contain spaces (i.e. use quotes), so it is easiest just to use simple names.

Building a driver is very simple once you have created a driver project. At this point you should have three files in your project directory - hello.c, sources and makefile. Open up the DDK Checked Build Environment (the DDK command-prompt) and type "build ":

?
C:\DRIVERS\HELLO> build

That's it! Assuming you installed the DDK correctly the driver will compile with no problems will be placed into the objchk/i386 directory.

Drivers and Services

Installing and executing a driver is quite a bit different than running a "normal" program from the command line. There is no concept of running a new process because a driver is a kernel module which lives permanently inside the system. Two steps are required to get a driver installed - registering the driver as a system service, and then starting the driver.

This is usually the area that confuses people who are not used to working with drivers. A driver is treated by Windows as a regular service which can be started and stopped just like any other service. The Windows component that controls all this is called the Service Control Manager. This component exposes an API which can be used to register, unregister, and start & stop drivers and regular Win32 services.

Actually registering a driver and starting it can be a little confusing if you've not done it before. But even if you are familiar with these concepts I advise anyone who hasn't done so to visit www.osronline.com and download their Driver Loader tool from the downloads section. This handy tool allows you to register, start, stop and unregister drivers, all from a single GUI.

Installing a Driver

There are two ways to register a driver as a system service. The first (and easist) is to use the CreateService API which is documented in MSDN. But basically all this does is create a few values in the Registry on your behalf. The second method is to manually create these values in the following registry location:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\<driver name>

You need to make a new subkey with the name of your service (in this case the service is a kernel driver), so you could call it "hello" for instance. Inside this registry key you need supply the name, path and start-type of the driver, using the following registry values:

Registry Value | Description | Example
DisplayName | Name of your driver as it appears in the service list | hello
ImagePath | Full NT-style path to the driver (or just the filename if it lives in system32\drivers). | \??\C:\DRIVERS\HELLO\hello.sys
Start | How the driver can be started. For testing the recommended value is Demand (3), which indicates that the driver can only be started manually. The other start values - Boot (0), System (1) and Auto (2) instruct Windows to load the driver at various points during the system startup. The last value - Disabled (4) - prevents the driver from loading at all. | 3
Type | The type of service. Basic kernel drivers must have a value of "1" here. | 1

Be very careful what value you give to the Start item. For testing purposes you should only ever use Demand (or Manual as it is also known). All the other options (System, Boot and Automatic) cause the driver to be loaded during boot-time - which can be fatal if your driver has a bug in it because you may never recover your system.

Note that a driver only needs to be registered once (it can only be registered once) - and can be started and stopped as many times as you like after this. I would advise you at this stage to use the OSR Driver Loader to register your driver.

Starting a Driver

Once a driver has been registered as a system-service, it can be loaded (and unloaded) using the Service Control Manager. You can start a driver programmatically using the StartService API call, but it is far easier to goto the command-prompt and type:

?
net start hello

The following output will then be displayed:

?
The hello service was started successfully.

Nothing else will appear to happen though because drivers don't (and can't) output any data to the console.

Note that at this point the driver has been loaded into kernel-space and your DriverEntry function has been executed. If you have gotten this far then you haven't blue-screened your computer - so congratulations! Maybe for the first time code that you have written is now in the area of system memory that is inaccessible to usermode programs (address range 0x80000000 and above).

Viewing Driver Output

For debugging purposes it is common for drivers to use the DbgPrint API to emit debug messages:

?
ULONG DbgPrint(const char *fmt, ...);

DbgPrint is very similar to the regular printf routine, the difference being that it can only be called from kernel mode. The debug messages, instead of displaying on the screen, are sent to the kernel debugger (usually WinDbg), assuming that one is attached.

However there are a variety of programs on the internet that can capture these kernel messages without having to use a debugger. One of the best is DebugView from SysInternals, shown above. Note that I am currently working on a version of my own (with complete source-code) which you will soon be able to download from this site.

Unloading a Driver

Stopping (and unloading) a driver is as simple as starting it:

?
net stop hello

However the driver we have built at this point will never unload, because we have omitted a tiny detail. The one thing we left out was the DriverUnload routine, which the service-control-manager calls when a driver is about to unload. This unload routine must be specified during DriverEntry if our driver is to be unloadable, and a pointer to the routine stored in the DriverObject:

?
#include <ntddk.h>
void DriverUnload(PDRIVER_OBJECT pDriverObject)
{
    DbgPrint("Driver unloading\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    DriverObject->DriverUnload = DriverUnload;
    DbgPrint("Hello, World\n");
    return STATUS_SUCCESS;
}

You will have to rebuild the driver and reboot your computer, but now you will be able to use "net start" and "net stop" as often as you like to start and stop the driver.

Catch22 Driver Loader

I have stated previously that it is possible to register and start a driver programmatically - i.e. from your own usermode application. The API is part of the Service Control Manager API - functions such as OpenSCManager, CreateService, StartService, StopService, CloseServiceHandle are all used together to achieve this goal.

The second sourcecode download at the top of this tutorial is a Win32 console application, which demonstrates how a program can extract a driver from it's own resources and drop it to disk. Once extracted it starts and then stops the driver before cleaning up afterwards - all using the Service Control Manager API.

This is a very useful little program which provides a good framework for usermode projects requiring the use of a device-driver, so take a look at the sources and give it a whirl - you should be able to replace the sample driver with your own version and carry on using the same program.

Related Reading

There are many books on Device Driver development for Windows. My favourite is "Windows NT Device Driver Development" by Peter Viscarola and W. Anthony Mason. Both of these authors work for Open System Resources and really know their stuff. Other books worth reading are listed below.

Programming the Microsoft Windows Driver Model 2nd Edition - by Walter Oney.Microsoft Windows Internals 4th Edition - by Mark Russinovich.Undocumented Windows 2000 Secrets - by Sven Schreiber.Undocumented Windows NT - by Prasad Dabak, Sandeep Phadke and Milind Borate.Windows NT/2000 Native API Reference - by Gary Nebbett.

All these books contain vital information for kernel programming and device-driver writers.

Conclusion

That pretty much covers the first steps necessary to build, install and start a device driver in Windows NT/2000/XP. Note that at no point have we needed to write complicated INF files (driver-installation files), nor have we needed to use driver signing wizards or complicated GUIs. These things are only necessary for more complex WDM drivers which have additional requirements for their installation. For our simple NT4 legacy driver we can avoid all this hassle and install the driver ourselves.

I strongly advise anyone doing driver development to invest in a separate debugging machine on which to test your drivers - it's just not worth risking your main development machine with a buggy driver. When a driver crashes, it really crashes - the infamous Blue Screen Of Death will become a very familiar site to you as you are learning device-driver programming - I guarantee it!

Well hopefully you have found this tutorial useful, if you have any feedback I'd love to hear it. Don't forget to check out the downloads at the top of this tutorial - they're very useful!

25 Mar 2005

Posted in Tutorials

win32
Powered by Drupal


Remote Debugging using VMWare | Catch22

Skip to main content
Win32 Software & Programming Tutorials
  • Home
  • Software
  • Source
  • Tutorials
  • Contact

Remote Debugging using VMWare

How to debug a device-driver using WinDbg and VMWare

Introduction

There are two ways to debug a device-driver in Windows. The first is to install the driver on your development machine and run a local kernel-mode debugger such as SoftIce. Although SoftIce is a very powerful tool, debugging a driver on your development machine is not a good idea because you will be guaranteed to destroy all your work if the driver crashes the system. You really need to install your driver on a separate machine and use remote kernel-debugging with a null-modem serial cable to protect your development box.

However if you are like me and either can't afford a separate machine, or simply don't have the space to keep one then keep reading because there is a nice solution for you. We will be using virtual-machine technology (i.e. VMWare or VirtualPC), and using named pipes to emulate the serial-port connections.

Installing WinDbg

The preferred debugging method by most developers is to use Microsoft WinDbg - firstly because it is free - but more importantly because it is an incredibly powerful kernel-mode debugger which can be used to remotely debug a target machine via a null-modem serial cable. Follow the instructions below to install and configure WinDbg for remote debugging:

  1. Install WinDbg from the following location:

    http://www.microsoft.com/whdc/devtools/debugging/default.mspx

  2. Open the Symbol Search Path dialog from the file menu:

    File -> Symbol File Path... (Ctrl+S)

  3. Configure WinDbg to use Microsoft's online Symbol Server and cache debug symbols locally:

    srv*c:\windows\symbols*http://msdl.microsoft.com/download/symbols

  4. Create a shortcut on your desktop to WinDbg, and specify the following command line:

    windbg -b -k com:pipe,port=\\.\pipe\com_1,resets=0

Once you have configured WinDbg, use the shortcut you created in step#4 to attach to a "remote" machine which has debugging enabled. In order to debug part of your driver, you will usually embed an "int 3" to make the debugger break. And if you installed the debug version of your driver, WinDbg will automatically find the corresponding source-file for that driver and step through it line-by-line.

Configure VMWare to be the remote host

VMWare is an amazing product which allows you to run a complete OS inside a single window on your PC. The product is far cheaper than buying a separate machine and is infinitely more flexible, because you can host multiple OS images (saved as files on your hard-disk). For example, you could have every version of Windows available to you for testing, all on one physical PC!

Once you have VMWare installed (note: VirtualPC from Microsoft will also work, but it isn't quite as good), follow the steps below to enable the virtual machine to act as a kernel-debug target for WinDbg. The idea is that instead of using a null-modem cable between physical machines, we can set up a named pipe and attach it to the virtual machine's COM port. WinDbg will then attach to the named pipe, and remote-debug the hosted OS inside VMWare.

The sequence of steps below was originally found at http://silverstr.ufies.org/lotr0 so I have reproduced them here.

  1. Open the Configuration Editor from the Settings menu
  2. Click the Add button to open the Add Hardware Wizard.
  3. Select Serial Port and click the Next button.
  4. Select Use Named Pipe, and change the name to:

    \\.\pipe\com_1

  5. Select This end is the server.
  6. Select The other end is an application.
  7. Click on Advanced, and select the "Yield on CPU poll" option.
  8. Click Finish and then OK to save your options. When you power up your VM image, the Devices->Serial0 menu will be available.
  9. When the virtual machine boots up, edit the boot.ini file inside the VM OS's root directory. Add a new OS line to tell the "virtual" Windows OS (when it boots) to enable it's kernel debugger. Two options need to be added to enable this feature:

    /debugport=com1 /baudrate=115200

    In practise you simply copy one of your OS configurations in boot.ini onto a new line, and add the additional two options onto the end:

    ?
    [boot loader]
    timeout=30
    default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
    [operating systems]
    multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /fastdetect
    multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional - DEBUG" /fastdetect /debugport=com1 /baudrate=115200

    Note that all the options should be on one line, not wrapped-around like they are above. Save boot.ini (it will probably be read-only so disable that first) and reboot.

  10. At this point take a SnapShot of your VM Image so that you can roll back to this state at any time.

Using VMWare's SnapShot feature is incredibly useful. Whenever your driver causes a blue-screen, you can simply Restore the VM Image and have another go! This saves an amazing amount of time - the repetitive task of rebooting, installing drivers and setting up the environment instantly disappears. If you get into this habit device driver development will be far less tiresome.

Debugging using WinDbg

To debug your driver simply reboot the VMWare OS. You will be presented with the OS loader menu which was modified by editing the boot.ini file. Select the DEBUG configuration and hit return!

Once the OS has started booting you can simply double-click your WinDbg shortcut and WinDbg will automatically attach itself to the VMWare session (using the named pipe "com_1") and enter the kernel-debug mode. Note that this isn't the same as running the debug version of Windows - we have simply enabled the guest OS's kernel debugger that is present in all Windows NT OSs. Once you have "broken in" to the debuggee, the entire virtual machine will be stopped - not even the mouse will be active inside VMWare.

At this point you can just hit F5 in WinDbg to let the hosted OS continue booting.

Conclusion

I strongly advise anyone doing software development, and especially those doing driver work, to invest in either VMWare or VirtualPC. It's a one-off payment and you won't regret spending the money. The flexibility and ease of use it gives you for remote debugging just cannot be emphasised enough. Although a stand-alone dedicated test machine is better, the VM solution can solve many of your debugging scenarios.

One important thing to mention is the use of debug symbols. At the top of this tutorial we configured WinDbg to use Microsoft's Debug Symbol Server. WinDbg will automatically connect to this server and download the appropriate debug symbols for your target operating system - so you won't have to manually install symbols.

Note that we do not require the debug version of Windows. There are two versions of any Windows product - Debug and Retail (or Normal). The debug build contains a special version of the kernel which can be used to aid debugging. This modified kernel contains extra code and sanity checks to help you debug the kernel and your drivers. Not only is it quite a bit slower than the normal OS, but the modified kernel behaviour can be unhelpful at times. All we require are the debug symbols for the Release version of Windows - which is where the symbol server comes in useful.

The last thing to mention is the use of SMP machines (dual/multi-processor machines). All good programmers understand how important it is to develop (and test) on a multi-processor machine. This is especially important with device-driver writing. The problem with VMWare is that it only uses one of your CPUs even if you have two installed** - so at some point you will have to bite the bullet and buy a dual-processor machine. But it will be worth it just for the extra horse power!

**Update: The latest versions of VMWare now do support SMP machines, but there is still no substitute for a real hardware when you're testing driver-code.

Well hopefully you have found this tutorial useful, if you have any feedback I'd love to hear it..

28 Mar 2005

Posted in Tutorials

win32
Powered by Drupal


C pour Windows - API de Windows

Reference API pour C Windows Description

Vu dans ... http://www.gladir.com

Nom de l'API | Bibliothèque | Description
AbnormalTermination |  | Cette fonction permet d'indiquer que le «TRY-FINALLY» d'un bloc s'est exécuté normalement.
AbortDoc | gdi32.dll | Cette fonction permet d'effectuer l'annulation d'une impression d'un document.
AbortPath | gdi32.dll | Cette fonction permet de terminer et de restituer toutes les palettes du périphérique de contexte.
AbortPrinter | winspool.drv | Cette fonction permet d'effacer un fichier dans le tampon d'impression si l'imprimante est configurée avec un tampon d'impression.
AbortSystemShutdownA | advapi32.dll | Cette fonction permet d'annuler l'opération de fermeture ou de redémarrage en cours d'exécution du système d'exploitation Windows.
AbortSystemShutdownW | advapi32.dll | Cette fonction permet d'annuler l'opération de fermeture ou de redémarrage en cours d'exécution du système d'exploitation Windows avec des paramètres de chaine de caractères étendue (Unicode).
AccessCheck | advapi32.dll | Cette fonction permet, à une application serveur, de vérifier les accès client à un objet de contrôle d'accès.
AccessCheckAndAuditAlarmA | advapi32.dll | Cette fonction permet d'effectuer une validation d'accès et de provoquer le message approprié.
AccessCheckAndAuditAlarmW | advapi32.dll | Cette fonction permet d'effectuer une validation d'accès et de provoquer le message approprié avec des paramètres de chaine de caractères étendue (Unicode).
AcsLan | dlcapi.dll | Cette fonction permet de communiquer avec d'autres ordinateurs ou par périphérique réseau, comme des imprimantes, en utilisant le protocole DLC (Data Link Control).
ActivateKeyboardLayout | user32.dll | Cette fonction permet d'activer une disposition différente du clavier pour le processus courant.
AddAccessAllowedAce | advapi32.dll | Cette fonction permet d'ajouter un accès autorisé à l'ACE d'un ACL.
AddAccessDeniedAce | advapi32.dll | Cette fonction permet d'ajouter un accès refusé à l'ACE d'un ACL.
AddAce | advapi32.dll | Cette fonction permet d'ajouter un ACE à un ACL spécifié.
AddAuditAccessAce | advapi32.dll | Cette fonction permet d'ajouter un ACE système à un ACL système.
AddAtomA | kernel32.dll | Cette fonction permet d'ajouter une chaine de caractères ASCIZ à l'Atom global.
AddAtomW | kernel32.dll | Cette fonction permet d'ajouter un caractère étendue (Unicode) à l'Atom global.
AddFontResourceA | gdi32.dll | Cette fonction permet d'ajouter une police de caractères à partir d'un fichier.
AddFontResourceW | gdi32.dll | Cette fonction permet d'ajouter une police de caractères à partir d'un fichier avec des paramètres de chaine de caractères étendues (Unicode).
AddFormA | winspool.drv | Cette fonction permet d'ajouter un formulaire à la liste des formulaires de l'imprimante spécifié.
AddInksetInterval | penwin32.dll | Cette fonction permet de fixer l'intervalle de la cartouche d'encre de l'imprimante.
AddJobA | winspool.drv | Cette fonction permet de fixer la chaine de caractères du répertoire contenant les fichiers du tampon d'imprimante.
AddMonitorA | winspool.drv | Cette fonction permet d'installer un moniteur d'imprimante local et des liens de configuration, de données et de fichiers du moniteur.
AddPenDataHRC | penwin32.dll | Cette fonction permet d'ajouter un objet de données de crayon lumineux.
AddPenInputHRC | penwin32.dll | Cette fonction permet d'ajouter un objet de données de crayon lumineux exporté.
AddPointsPenData | penwin32.dll | Cette fonction permet d'ajouter un ensemble de point de données à l'objet de crayon lumineux.
AddPortA | winspool.drv | Cette fonction permet d'ajouter un nom de port à la liste des ports supporté par l'imprimante.
AddPrinterA | winspool.drv | Cette fonction permet d'ajouter une imprimante à la liste des imprimantes.
AddPrinterConnectionA | winspool.drv | Cette fonction permet d'ajouter une connexion à l'imprimante pour l'utilisateur courant.
AddPrinterDriverA | winspool.drv | Cette fonction permet d'installer un pilote d'imprimante avec la configuration, les données et les fichiers du pilote.
AddPrintProcessorA | winspool.drv | Cette fonction permet d'installer un processeur d'imprimante à partir d'un serveur et d'une liste processeur d'imprimante supporté.
AddPrintProvidorA | winspool.drv | Cette fonction permet d'installer un fournisseur d'imprimante avec la configuration, les données et les fichiers du fournisseur.
AddWordsHWL | penwin32.dll | Cette fonction permet d'ajouter des mots dans une liste de mots.
AdjustTokenGroups | advapi32.dll | Cette fonction permet d'ajuster des groupes dans une liste d'objet à saisir.
AdjustTokenPrivileges | advapi32.dll | Cette fonction permet d'ajuster des prévilèges dans une liste d'objet à saisir.
AdjustWindowRect | user32.dll | Cette fonction permet de calculer l'espace requis par le rectangle d'une fenêtre basé sur un rectangle client.
AdjustWindowRectEx | user32.dll | Cette fonction permet de calculer l'espace requis par le rectangle d'une fenêtre avec un style étendue basé sur un rectangle client.
AdvancedDocumentPropertiesA | winspool.drv | Cette fonction permet d'afficher la configuration de l'imprimante spécifiée dans une boite de dialogue.
AllocateAndInitializeSid | advapi32.dll | Cette fonction permet d'alloué ainsi que d'initialiser l'identificateur de sécurité (SID) avec jusqu'à 8 sous-autorités.
AllocateLocallyUniqueId | advapi32.dll | Cette fonction permet d'alloué un identificateur local unique (LUID).
AllocConsole | kernel32.dll | Cette fonction permet de créer une nouvelle console.
AngleArc | gdi32.dll | Cette fonction permet de dessiner une ligne de segment et d'un arc de cercle.
AnimatePalette | gdi32.dll | Cette fonction permet de fixer la valeur des palettes de couleur logique.
AnyPopup | user32.dll | Cette fonction permet d'indiquer s'il existe un moins une fenêtre visible ou invisible sur le bureau de l'écran.
AppendMenuA | user32.dll | Cette fonction permet d'ajouter un nouvel item au menu courant.
AppendMenuW | user32.dll | Cette fonction permet d'ajouter un nouvel item au menu courant avec des paramètres de chaine de caractères étendues (Unicode).
Arc | gdi32.dll | Cette fonction permet de dessiner l'arc d'une ellipse.
ArcTo | gdi32.dll | Cette fonction permet de dessiner un arc d'une ellipse.
AreAllAccessesGranted | advapi32.dll | Cette fonction permet d'effectuer la vérification sur un ensemble de requête d'accès garantie.
AreAnyAccessesGranted | advapi32.dll | Cette fonction permet d'effectuer la vérification sur n'importe quel élément d'un ensemble de requête d'accès garantie.
AreFileApisANSI | kernel32.dll | Cette fonction permet d'indiquer si les fonctions pour fichier du Win32 utilise un ensemble de police de caractères ANSI ou une page OEM.
ArrangeIconicWindows | user32.dll | Cette fonction permet de minimiser les fenêtres enfants d'une fenêtre parente.
AttachThreadInput | user32.dll | Cette fonction permet de relier un processus avec un autre.
auxGetDevCapsA | winmm.dll | Cette fonction permet de demander les informations concernant le système audio auxiliaire.
auxGetNumDevs | winmm.dll | Cette fonction permet de demander le nombre de système audio auxiliaire reconnu par Windows.
BackupEventLogA | advapi32.dll | Cette fonction permet d'effectuer une sauvegarde du journal de bord dans un fichier de copie de sécurité.
BackupEventLogW | advapi32.dll | Cette fonction permet d'effectuer une sauvegarde du journal de bord dans un fichier de copie de sécurité avec des paramètres de chaine de caractères étendue (Unicode).
BackupRead | kernel32.dll | Cette fonction permet d'effectuer la lecture de flux de données associé avec un fichier ou un répertoire spécifié d'un tampon.
BackupSeek | kernel32.dll | Cette fonction permet de fixer la position dans le flux de données accessible par les fonctions «BackupRead» et «BackupWrite».
BackupWrite | kernel32.dll | Cette fonction permet d'effectuer l'écriture de flux de données associé avec un fichier ou un répertoire spécifié d'un tampon.
Beep | kernel32.dll | Cette fonction permet d'émettre un signal sonore sur le PC Speaker.
BeginDeferWindowPos | user32.dll | Cette fonction permet d'allouer de la mémoire sur de multiples positions de fenêtres d'une structure de données.
BeginPaint | user32.dll | Cette fonction permet de préparé les spécifications de la fenêtre pour la peindre et la remplir avec les informations de la structure spécifié.
BeginPath | gdi32.dll | Cette fonction permet d'ouvrir une braquette de chemin pour le périphérique de contexte (DC) spécifié.
BeginUpdateResourceA | kernel32.dll | Cette fonction permet de retourner l'identificateur Handle qu'utilise une fonction «UpdateResource» pour ajouter, supprimer ou remplacer dans un fichier exécutable.
BeginUpdateResourceW | kernel32.dll | Cette fonction permet de retourner l'identificateur Handle qu'utilise une fonction «UpdateResource» pour ajouter, supprimer ou remplacer dans un fichier exécutable avec des paramètres de chaine de caractères étendue (Unicode).
BitBlt | gdi32.dll | Cette fonction permet d'effectuer la copie d'un Bitmap vers l'écran.
BlockInput | user32.dll | Cette fonction permet d'arrêter les événements d'entrer comme le clavier, la souris,...
BringWindowToTop | user32.dll | Cette fonction permet d'activer et de porter, en haut de la pile des fenêtres, la fenêtre spécifiée.
BroadcastSystemMessageA | user32.dll | Cette fonction permet d'envoyer un message vers un récipient spécifié.
BroadcastSystemMessageW | user32.dll | Cette fonction permet d'envoyer un message vers un récipient spécifié avec des paramètres de chaine de caractères étendues (Unicode).
BuildCommDCBA | kernel32.dll | Cette fonction permet de remplir une structure DCB avec les valeurs spécifiées par une chaine de caractères de périphérique de contrôle.
BuildCommDCBW | kernel32.dll | Cette fonction permet de remplir une structure DCB avec les valeurs spécifiées par une chaine de caractères de périphérique de contrôle avec des paramètres de chaine de caractères étendue (Unicode).
BuildCommDCBAndTimeoutsA | kernel32.dll | Cette fonction permet de convertir la chaine de caractères des définitions d'un périphérique en un bloc de périphérique de contrôle et de placer les codes dans un bloc dans le périphérique de contrôle.
BuildCommDCBAndTimeoutsW | kernel32.dll | Cette fonction permet de convertir la chaine de caractères des définitions d'un périphérique en un bloc de périphérique de contrôle et de placer les codes dans un bloc dans le périphérique de contrôle avec des paramètres de chaine de caractères étendue (Unicode).
CallMsgFilterA | user32.dll | Cette fonction permet de passer un message à la fonction de filtre de message courant.
CallMsgFilterW | user32.dll | Cette fonction permet de passer un message à la fonction de filtre de message courant avec des paramètres de chaine de caractères étendues (Unicode).
CallNamedPipeA | kernel32.dll | Cette fonction permet d'effectuer une connection à l'aide d'un tuyau de type de message et de lire et d'écrire dans se tuyau et d'ensuite fermer le tuyau.
CallNamedPipeW | kernel32.dll | Cette fonction permet d'effectuer une connection à l'aide d'un tuyau de type de message et de lire et d'écrire dans se tuyau et d'ensuite fermer le tuyau avec des paramètres de chaine de caractères étendue (Unicode).
CallNextHookEx | user32.dll | Cette fonction permet de passer au prochain crochet des informations de crochet dans une chaine de crochet.
CallWindowProcA | user32.dll | Cette fonction permet d'appeler et de passer des informations de message à la fonction de fenêtre spécifié.
CallWindowProcW | user32.dll | Cette fonction permet d'appeler et de passer des informations de message à la fonction de fenêtre spécifié avec des paramètres de chaine de caractères étendues (Unicode).
CancelDC | gdi32.dll | Cette fonction permet d'annuler une opération en attente pour le périphérique de contexte (DC) spécifié.
CancelIo | kernel32.dll | Cette fonction permet d'annuler une opération d'entrée/sortie initié par un processus léger dans un fichier.
CancelWaitableTimer | kernel32.dll | Cette fonction permet de fixer le délai d'attente de l'horloge à un état inactif.
CascadeWindows | user32.dll | Cette fonction permet d'effectuer des cascades de fenêtre spécifié ou de fenêtre enfant dans la fenêtre parent spécifié.
ChangeClipboardChain | user32.dll | Cette fonction permet d'enlever le premier élément de la chaine du visualisateur de presse-papier et de le remplacer par le deuxième élément spécifié.
ChangeDisplaySettingsA | user32.dll | Cette fonction permet de modifier les paramètres du mode d'affichage graphique
ChangeDisplaySettingsW | user32.dll | Cette fonction permet de modifier les paramètres du mode d'affichage graphique avec des paramètres de chaine de caractères étendues (Unicode).
ChangeDisplaySettingsExA | user32.dll | Cette fonction permet de modifier les paramètres du mode d'affichage graphique pour le périphérique d'affichage spécifié.
ChangeDisplaySettingsExW | user32.dll | Cette fonction permet de modifier les paramètres du mode d'affichage graphique pour le périphérique d'affichage spécifié avec des paramètres de chaine de caractères étendues (Unicode).
ChangeMenuA | user32.dll | Cette fonction permet de modifier un élément du menu spécifié.
ChangeMenuW | user32.dll | Cette fonction permet de modifier un élément du menu spécifié avec des paramètres de chaine de caractères étendues (Unicode).
CharLowerA | user32.dll | Cette fonction permet d'effectuer la conversion d'une chaine de caractères spécifié en minuscule.
CharLowerBuffA | user32.dll | Cette fonction permet d'effectuer la conversion des caractères majuscules contenu dans un tampon en caractères minuscule.
CharLowerBuffW | user32.dll | Cette fonction permet d'effectuer la conversion des caractères majuscules contenu dans un tampon en caractères minuscule avec des paramètres de chaine de caractères étendues (Unicode).
CharLowerW | user32.dll | Cette fonction permet d'effectuer la conversion d'une chaine de caractères spécifié en minuscule avec des paramètres de chaine de caractères étendues (Unicode).
CharNextA | user32.dll | Cette fonction permet de retourner un pointeur sur le prochaine caractère contenu dans une chaine de caractères.
CharNextExA | user32.dll | Cette fonction permet de retourner l'adresse du prochaine caractère contenu dans une chaine de caractères.
CharNextW | user32.dll | Cette fonction permet de retourner un pointeur sur le prochaine caractère contenu dans une chaine de caractères avec des paramètres de chaine de caractères étendues (Unicode).
CharPrevA | user32.dll | Cette fonction permet de retourner un pointeur sur le caractère précédent contenu dans une chaine de caractères.
CharPrevExA | user32.dll | Cette fonction permet de retourner l'adresse du caractère précédent contenu dans une chaine de caractères.
CharPrevW | user32.dll | Cette fonction permet de retourner un pointeur sur le caractère précédent contenu dans une chaine de caractères avec des paramètres de chaine de caractères étendues (Unicode).
CharToOemA | user32.dll | Cette fonction permet d'effectuer la transformation d'une chaine de caractères en ensemble de caractères OEM utilisateur.
CharToOemBuffA | user32.dll | Cette fonction permet d'effectuer la transformation d'un nombre spécifique de caractères dans une chaine de caractères d'un ensemble de caractères OEM utilisateur.
CharToOemBuffW | user32.dll | Cette fonction permet d'effectuer la transformation d'un nombre spécifique de caractères dans une chaine de caractères d'un ensemble de caractères OEM utilisateur avec des paramètres de chaine de caractères étendues (Unicode).
CharToOemW | user32.dll | Cette fonction permet d'effectuer la transformation d'une chaine de caractères en ensemble de caractères OEM utilisateur avec des paramètres de chaine de caractères étendues (Unicode).
CharUpperA | user32.dll | Cette fonction permet d'effectuer la conversion d'une chaine de caractères spécifié en majuscule.
CharUpperBuffA | user32.dll | Cette fonction permet d'effectuer la conversion des caractères minuscules contenu dans un tampon en caractères majuscule.
CharUpperBuffW | user32.dll | Cette fonction permet d'effectuer la conversion des caractères minuscules contenu dans un tampon en caractères majuscule avec des paramètres de chaine de caractères étendues (Unicode).
CharUpperW | user32.dll | Cette fonction permet d'effectuer la conversion d'une chaine de caractères spécifié en majuscule avec des paramètres de chaine de caractères étendues (Unicode).
CheckColorsInGamut | gdi32.dll | Cette fonction permet d'indiquer si les valeurs de couleurs spécifiés sont dans la gamme du périphérique spécifié.
CheckDlgButton | user32.dll | Cette fonction permet de fixer ou d'enlever une marque de vérification d'un bouton de contrôle ou de changer l'état d'un contrôle d'arbre de bouton.
CheckMenuItem | user32.dll | Cette fonction permet de fixer ou d'enlever un marque de vérification d'un item de menu dans un menu contextuel.
CheckMenuRadioItem | user32.dll | Cette fonction permet de vérifier l'item de menu spécifié et de fabriquer un item radio.
CheckRadioButton | user32.dll | Cette fonction permet de vérifier ou d'enlever un marque de vérification d'un groupe de bouton radio spécifié.
ChildWindowFromPoint | user32.dll | Cette fonction permet de demander le propriétaire de la fenêtre enfant à l'aide d'une coordonnée spécifié.
ChildWindowFromPointEx | user32.dll | Cette fonction permet de demander lequel des fenêtres enfants, si possible, appartient à la fenêtre parent du point spécifié.
ChooseColorA | comdlg32.dll | Cette fonction permet de lancer une boite de dialogue offrant la possibilité à l'utilisateur de choisir une couleur
ChooseFontA | comdlg32.dll | Cette fonction permet de lancer une boite de dialogue offrant la possibilité à l'utilisateur de choisir une police de caractères
ChoosePixelFormat | gdi32.dll | Cette fonction permet de tenter de faire correspondre à un format de pixel approprié supporté par un périphérique de contexte d'un format de pixel donné spécifié.
Chord | gdi32.dll | Cette fonction permet de dessiner la corde de limite à l'aide d'une intersection d'une ellipse avec le centre d'un rectangle et d'une ligne de segment.
ClearCommBreak | kernel32.dll | Cette fonction permet de restituer les caractères transmis par un périphérique de communications spécifié et place ligne de transmission dans un état non arrêtable.
ClearCommError | kernel32.dll | Cette fonction permet de demander des informations à propos d'erreur de communications et un rapport d'état actuel sur un périphérique de communication.
ClearEventLogA | advapi32.dll | Cette fonction permet d'effacer le journal de bord spécifié et sauvegarde une copie du journal du bord courant dans un fichier si désiré.
ClearEventLogW | advapi32.dll | Cette fonction permet d'effacer le journal de bord spécifié et sauvegarde une copie du journal du bord courant dans un fichier si désiré avec des paramètres de chaine de caractères étendue (Unicode).
ClientToScreen | user32.dll | Cette fonction permet d'effectuer la transformation des coordonnées logique du client en coordonnées physiquement sur l'écran
ClipCursor | user32.dll | Cette fonction permet de définir une fenêtre limitant le déplacement du pointeur de la souris.
CloseClipboard | user32.dll | Cette fonction permet d'indiquer que le traitement avec le presse-papier est terminé et que les autres applications peuvent maintenant avoir accès aux données qu'elle contient.
CloseDesktop | user32.dll | Cette fonction permet de fermer ou d'ouvrir le Handle de l'objet de bureau.
CloseEnhMetaFile | gdi32.dll | Cette fonction permet de fermer un périphérique de contexte de fichier méta étendue et retourne le Handle identifiant un format de fichier méta étendue.
CloseEventLog | advapi32.dll | Cette fonction permet de fermer le fichier de journal de bord d'événement spécifié
CloseFigure | gdi32.dll | Cette fonction permet de fermer une figure dans une spécification de chemin.
CloseHandle | kernel32.dll | Cette fonction permet d'effectuer la fermeture d'un Handle.
CloseMetaFile | gdi32.dll | Cette fonction permet de fermer et de créer le fichier de méta pour pouvoir jouer son contenu.
ClosePrinter | winspool.drv | Cette fonction permet d'indiquer que le traitement avec le tampon d'impression de l'imprimante est terminé.
CloseWindow | user32.dll | Cette fonction permet de minimiser (sans la détruire) la fenêtre spécifié.
CloseWindowStation | user32.dll | Cette fonction permet de fermer un Handle de fenêtre de station ouverte.
CoCreateGuid | ole32.dll | Cette fonction permet de créer un identificateur global unique GUID (Global Unique ID) générer sur une base de 128-bits pouvant être utilisé par exemple pour les identificateurs de classe CLSID.
ColorMatchToTarget | gdi32.dll | Cette fonction permet d'activer ou désactiver le prévisualisation du périphérique de contexte spécifié.
CombineRgn | gdi32.dll | Cette fonction permet de créer une nouvelle région pour combiné deux régions existantes.
CombineTransform | gdi32.dll | Cette fonction permet d'effectuer la concaténation de double espace mondiale vers une transformation d'espace de page.
CommConfigDialogA | kernel32.dll | Cette fonction permet d'afficher une configuration de pilote de fournisseur dans une boite de dialogue.
CommConfigDialogW | kernel32.dll | Cette fonction permet d'afficher une configuration de pilote de fournisseur dans une boite de dialogue avec des paramètres de chaine de caractères étendue (Unicode).
CompareFileTime | kernel32.dll | Cette fonction permet de comparer deux temps de fichier de 64-bits.
CompareStringA | kernel32.dll | Cette fonction permet d'effectuer la comparaison de deux chaines de caractères ASCIZ.
CompareStringW | kernel32.dll | Cette fonction permet d'effectuer la comparaison de deux chaines de caractères Unicode.
ConfigurePortA | winspool.drv | Cette fonction permet de lancer une boite de dialogue offrant la possibilité à l'utilisateur de choisir le port parallèle ou USB d'imprimante
ConnectNamedPipe | kernel32.dll | Cette fonction permet d'activé un nom de tuyau d'un processus serveur en attente pour un processus client de connexion.
ContinueDebugEvent | kernel32.dll | Cette fonction permet d'activer un débogueur pour qu'il continue un processus précédemment rapporter comme un bogue d'événement.
ConvertDefaultLocale | kernel32.dll | Cette fonction permet de convertir une valeur locale spécial par défaut en un identificateur local courant.
ConvertThreadToFiber | kernel32.dll | Cette fonction permet d'effectuer la conversion d'un processus léger vers un fibre.
CopyAcceleratorTableA | user32.dll | Cette fonction permet de copier la table d'accélérateur spécifié.
CopyAcceleratorTableW | user32.dll | Cette fonction permet de copier la table d'accélérateur spécifié avec des paramètres de chaine de caractères étendues (Unicode).
CopyEnhMetaFileA | gdi32.dll | Cette fonction permet de copier le contenu d'un fichier méta de format étendue vers un fichier spécifié.
CopyEnhMetaFileW | gdi32.dll | Cette fonction permet de copier le contenu d'un fichier méta de format étendue vers un fichier spécifié avec des paramètres de chaine de caractères étendues (Unicode).
CopyFileA | kernel32.dll | Cette fonction permet d'effectuer la copie d'un fichier d'un emplacement à un autre emplacement
CopyFileExA | kernel32.dll | Cette fonction permet d'effectuer la copie d'un fichier d'un emplacement à un autre emplacement tout en effectuant un processus d'arrière plan indiquant sa progression.
CopyFileExW | kernel32.dll | Cette fonction permet d'effectuer la copie d'un fichier d'un emplacement à un autre emplacement tout en effectuant un processus d'arrière plan indiquant sa progression avec des paramètres de chaine de caractères étendue (Unicode).
CopyFileW | kernel32.dll | Cette fonction permet d'effectuer la copie d'un fichier d'un emplacement à un autre emplacement avec des paramètres de chaine de caractères étendue (Unicode).
CopyIcon | user32.dll | Cette fonction permet de copier un icône spécifié d'un autre module vers le module courant.
CopyImage | user32.dll | Cette fonction permet de créer une nouvelle image (icône, image ou Bitmap) et de copier les attributs de l'image donnée dans une nouvelle image.
CopyMetaFileA | gdi32.dll | Cette fonction permet de copier le contenu d'un format de méta fichier Windows vers un fichier spécifié.
CopyMetaFileW | gdi32.dll | Cette fonction permet de copier le contenu d'un format de méta fichier Windows vers un fichier spécifié avec des paramètres de chaine de caractères étendues (Unicode).
CopyRect | user32.dll | Cette fonction permet d'effectuer la copie des coordonnées d'un rectangle dans un autre rectangle.
CopySid | advapi32.dll | Cette fonction permet de copier l'identificateur de sécurité (SID) dans un tampon spécifié.
CountClipboardFormats | user32.dll | Cette fonction permet de demander le nombre de différentes données de formats courant que contient le presse-papier.
CreateAcceleratorTableA | user32.dll | Cette fonction permet de créer une table d'accélérateur.
CreateAcceleratorTableW | user32.dll | Cette fonction permet de créer une table d'accélérateur avec des paramètres de chaine de caractères étendues (Unicode).
CreateBitmap | gdi32.dll | Cette fonction permet de créer un périphérique de Bitmap en mémoire avec la largeur, hauteur et la palette de bits spécifié.
CreateBitmapIndirect | gdi32.dll | Cette fonction permet de créer un Bitmap avec la largeur, la hauteur et la palette de bits spécifié dans une structure BITMAP.
CreateBrushIndirect | gdi32.dll | Cette fonction permet de créer une brosse avec le style, la couleur et la palette spécifié dans la structure «LOGBRUSH».
CreateCaret | user32.dll | Cette fonction permet de créer un nouveau signe pour le curseur texte et associe le propriétaire à la fenêtre spécifié.
CreateColorSpaceA | gdi32.dll | Cette fonction permet de créer un espace de couleur logique.
CreateColorSpaceW | gdi32.dll | Cette fonction permet de créer un espace de couleur logique avec des paramètres de chaine de caractères étendues (Unicode).
CreateCompatibleBitmap | gdi32.dll | Cette fonction permet de créer un Bitmap compatible avec le périphérique spécifié.
CreateCompatibleDC | gdi32.dll | Cette fonction permet de créer un contexte de périphérique mémoire compatible avec le périphérique spécifié.
CreateConsoleScreenBuffer | kernel32.dll | Cette fonction permet de créer un tampon de console écran et de retourner un identificateur Handle associée à celui-ci.
CreateCursor | user32.dll | Cette fonction permet d'effectuer la création d'une nouvelle image de pointeur de souris
CreateDCA | gdi32.dll | Cette fonction permet d'effectuer la création d'un objet de contexte (DC)
CreateDCW | gdi32.dll | Cette fonction permet d'effectuer la création d'un objet de contexte (DC) avec des paramètres de chaines de caractères étendues (Unicode).
CreateDesktopA | user32.dll | Cette fonction permet de créer un nouveau bureau dans une fenêtre de station associé avec le processus appelant.
CreateDesktopW | user32.dll | Cette fonction permet de créer un nouveau bureau dans une fenêtre de station associé avec le processus appelant avec des paramètres de chaine de caractères étendues (Unicode).
CreateDialogIndirectParamA | user32.dll | Cette fonction permet de créer un modèle de boite de dialogue pour un gabarit de boite de dialogue en mémoire et après son affichage, la fonction passe une valeur d'application à la procédure de boite de dialogue avec les paramètres spécifié du message «WM_INITDIALOG».
CreateDialogIndirectParamW | user32.dll | Cette fonction permet de créer un modèle de boite de dialogue pour un gabarit de boite de dialogue en mémoire et après son affichage, la fonction passe une valeur d'application à la procédure de boite de dialogue avec les paramètres spécifié du message «WM_INITDIALOG» avec des paramètres de chaine de caractères étendues (Unicode).
CreateDialogParamA | user32.dll | Cette fonction permet de créer un modèle de boite de dialogue pour une ressource de gabarit de boite de dialogue et après son affichage, la fonction passe une valeur d'application à la procédure de boite de dialogue avec les paramètres spécifié du message «WM_INITDIALOG».
CreateDialogParamW | user32.dll | Cette fonction permet de créer un modèle de boite de dialogue pour une ressource de gabarit de boite de dialogue et après son affichage, la fonction passe une valeur d'application à la procédure de boite de dialogue avec les paramètres spécifié du message «WM_INITDIALOG» avec des paramètres de chaine de caractères étendues (Unicode).
CreateDIBPatternBrush | gdi32.dll | Cette fonction permet de créer une brosse avec la palette spécifié par un périphérique de Bitmap indépendant (DIB).
CreateDIBPatternBrushPt | gdi32.dll | Cette fonction permet de créer une brosse logique avec la palette spécifié par un périphérique de Bitmap indépendant (DIB).
CreateDIBSection | gdi32.dll | Cette fonction permet de créer un Bitmap de périphérique indépendant (DIB) avec les applications pouvant y être écrit directement.
CreateDIBitmap | gdi32.dll | Cette fonction permet de créer un périphérique mémoire spécifié pour un périphérique de Bitmap indépendant (DIB) spécifié et un ensemble de bits optionnel pour le Bitmap.
CreateDirectoryA | kernel32.dll | Cette fonction permet d'effectuer la création d'un répertoire
CreateDirectoryW | kernel32.dll | Cette fonction permet d'effectuer la création d'un répertoire avec des paramètres de chaine de caractères étendue (Unicode).
CreateDirectoryExA | kernel32.dll | Cette fonction permet d'effectuer la création d'un répertoire étendue
CreateDirectoryExW | kernel32.dll | Cette fonction permet d'effectuer la création d'un répertoire étendue avec des paramètres de chaine de caractères étendue (Unicode).
CreateDirectoryTransactedA | kernel32.dll | Cette fonction permet d'effectuer la création d'un répertoire dans une opération de transaction
CreateDirectoryTransactedW | kernel32.dll | Cette fonction permet d'effectuer la création d'un répertoire dans une opération de transaction avec des paramètres de chaine de caractères étendue (Unicode).
CreateDiscardableBitmap | gdi32.dll | Cette fonction permet de créer un Bitmap compatible avec un périphérique spécifié.
CreateEllipticRgn | gdi32.dll | Cette fonction permet de créer une région elliptique.
CreateEllipticRgnIndirect | gdi32.dll | Cette fonction permet de créer une région elliptique à l'aide d'une structure de données.
CreateEnhMetaFileA | gdi32.dll | Cette fonction permet de créer un contexte de périphérique pour un méta fichier de format étendue.
CreateEnhMetaFileW | gdi32.dll | Cette fonction permet de créer un contexte de périphérique pour un méta fichier de format étendue avec des paramètres de chaine de caractères étendues (Unicode).
CreateEventA | kernel32.dll | Cette fonction permet de créer un objet d'événement nommé ou non-nommé.
CreateEventW | kernel32.dll | Cette fonction permet de créer un objet d'événement nommé ou non-nommé avec des paramètres de chaine de caractères étendue (Unicode).
CreateFiber | kernel32.dll | Cette fonction permet d'allouer un objet de fibre, en lui attribuant une pile, et en fixant le début de l'exécutant à l'adresse de début spécifié.
CreateFileA | kernel32.dll | Cette fonction permet d'effectuer la création ou l'ouverture d'un fichier ou d'un périphérique
CreateFileW | kernel32.dll | Cette fonction permet d'effectuer la création ou l'ouverture d'un fichier ou d'un périphérique avec des paramètres de chaine de caractères étendue (Unicode).
CreateFileMappingA | kernel32.dll | Cette fonction permet de créer un objet mapping, nommé ou non-nommé, pour un fichier spécifié.
CreateFileMappingW | kernel32.dll | Cette fonction permet de créer un objet mapping, nommé ou non-nommé, pour un fichier spécifié avec des paramètres de chaine de caractères étendue (Unicode).
CreateFontA | gdi32.dll | Cette fonction permet d'effectuer la création d'une police de caractères logique avec les caractéristiques spécifiés.
CreateFontIndirectA | gdi32.dll | Cette fonction permet de créer une police de caractères logique avec les caractéristiques spécifié dans la structure spécifié.
CreateFontIndirectW | gdi32.dll | Cette fonction permet de créer une police de caractères logique avec les caractéristiques spécifié dans la structure spécifié avec les paramètres de chaines de caractères étendues (Unicode).
CreateFontW | gdi32.dll | Cette fonction permet d'effectuer la création d'une police de caractères logique avec les caractéristiques spécifiés avec les paramètres de chaine de caractères étendues (Unicode).
CreateHalftonePalette | gdi32.dll | Cette fonction permet de créer une demi-teinte d'une palette pour le périphérique de contexte spécifié.
CreateHatchBrush | gdi32.dll | Cette fonction permet de créer une brosse logique spécifiant un motif de hachure et de couleur.
CreateICA | gdi32.dll | Cette fonction permet de créer une information de contexte pour un périphérique spécifié.
CreateICW | gdi32.dll | Cette fonction permet de créer une information de contexte pour un périphérique spécifié avec des paramètres de chaines de caractères étendues (Unicode).
CreateIcon | user32.dll | Cette fonction permet d'effectuer la création d'une icône
CreateIconFromResource | user32.dll | Cette fonction permet d'effectuer la création d'un icône ou d'un curseur à partir de bits de ressources décrit dans l'icône.
CreateIconFromResourceEx | user32.dll | Cette fonction permet d'effectuer la création d'un icône ou d'un curseur à partir de bits de ressources décrit dans l'icône avec des options complémentaires.
CreateIconIndirect | user32.dll | Cette fonction permet d'effectuer la création d'un icône ou d'un curseur à partir d'une structure de données.
CreateMailslotA | kernel32.dll | Cette fonction permet de créer une slot de courriel avec un nom spécifié et retourne un identificateur Handle associé à celle-ci.
CreateMailslotW | kernel32.dll | Cette fonction permet de créer une slot de courriel avec un nom spécifié et retourne un identificateur Handle associé à celle-ci avec des paramètres de chaine de caractères étendue (Unicode).
CreateMDIWindowA | user32.dll | Cette fonction permet d'effectuer la création de fenêtre enfant MDI (Interface de multiple document).
CreateMDIWindowW | user32.dll | Cette fonction permet d'effectuer la création de fenêtre enfant MDI (Interface de multiple document) avec des paramètres de chaine de caractères étendues (Unicode).
CreateMenu | user32.dll | Cette fonction permet d'effectuer la création d'un menu.
CreateMetaFileA | gdi32.dll | Cette fonction permet de créer un périphérique de contexte pour un format de fichier méta Windows.
CreateMetaFileW | gdi32.dll | Cette fonction permet de créer un périphérique de contexte pour un format de fichier méta Windows avec des paramètres de chaines de caractères étendues (Unicode).
CreateMutexA | kernel32.dll | Cette fonction permet de créer un objet Mutex nommé ou non-nommé.
CreateMutexW | kernel32.dll | Cette fonction permet de créer un objet Mutex nommé ou non-nommé avec des paramètres de chaine de caractères étendue (Unicode).
CreateNamedPipeA | kernel32.dll | Cette fonction permet de créer un tuyau avec un instance de nom spécifié et de retourner un identificateur Handle associé à celle-ci.
CreateNamedPipeW | kernel32.dll | Cette fonction permet de créer un tuyau avec un instance de nom spécifié et de retourner un identificateur Handle associé à celle-ci avec des paramètres de chaine de caractères étendue (Unicode).
CreatePalette | gdi32.dll | Cette fonction permet d'effectuer la création d'une palette logique.
CreatePatternBrush | gdi32.dll | Cette fonction permet d'effectuer la création d'une brosse logique avec la palette Bitmap spécifié.
CreatePen | gdi32.dll | Cette fonction permet d'effectuer la création d'un crayon logique avec le style, la largeur et la couleur spécifié.
CreatePenIndirect | gdi32.dll | Cette fonction permet d'effectuer la création d'un crayon logique avec le style, la largeur et la couleur à l'aide la structure de données spécifié.
CreatePipe | kernel32.dll | Cette fonction permet de créer un tuyau anonyme et de retourner un identificateur Handle attribué en lecture et écriture pour celle-ci.
CreatePolyPolygonRgn | gdi32.dll | Cette fonction permet d'effectuer la création d'une région contenant une série de fermeture de polygone.
CreatePolygonRgn | gdi32.dll | Cette fonction permet d'effectuer la création d'une région en forme de polygone.
CreatePopupMenu | user32.dll | Cette fonction permet d'effectuer la création d'un menu contextuel
CreatePrivateObjectSecurity | advapi32.dll | Cette fonction permet d'alloué et d'initialiser un descripteur de sécurité relatif à l'objet pour un un serveur d'objet protégé.
CreateProcessA | kernel32.dll | Cette fonction permet de créer un nouveau processus et de le fixer comme processus léger primaire.
CreateProcessW | kernel32.dll | Cette fonction permet de créer un nouveau processus et de le fixer comme processus léger primaire avec des paramètres de chaine de caractères étendue (Unicode).
CreateProcessAsUserA | advapi32.dll | Cette fonction permet de créer un nouveau processus et de le mettre comme processus primaire.
CreateProcessAsUserW | advapi32.dll | Cette fonction permet de créer un nouveau processus et de le mettre comme processus primaire avec des paramètres de chaine de caractères étendue (Unicode).
CreateRectRgn | gdi32.dll | Cette fonction permet d'effectuer la création d'une région rectangulaire.
CreateRectRgnIndirect | gdi32.dll | Cette fonction permet d'effectuer la création d'une région rectangulaire à l'aide d'une structure de données.
CreateRemoteThread | kernel32.dll | Cette fonction permet de créer un processus léger exécuté dans l'espace d'une adresse d'un autre processus.
CreateRoundRectRgn | gdi32.dll | Cette fonction permet d'effectuer la création d'une région rectangulaire avec les coins arrondies.
CreateScalableFontResourceA | gdi32.dll | Cette fonction permet de créer un fichier de ressource de police de caractères avec une police de caractères vectorielle.
CreateScalableFontResourceW | gdi32.dll | Cette fonction permet de créer un fichier de ressource de police de caractères avec une police de caractères vectorielle avec des paramètres de chaine de caractères étendue (Unicode).
CreateSemaphoreA | kernel32.dll | Cette fonction permet de créer un objet de sémaphore nommé ou non-nommé.
CreateSemaphoreW | kernel32.dll | Cette fonction permet de créer un objet de sémaphore nommé ou non-nommé avec des paramètres de chaine de caractères étendue (Unicode).
CreateSolidBrush | gdi32.dll | Cette fonction permet d'effectuer la création d'une brosse logique avec la couleur et le motif solide spécifié.
CreateStatusWindowA | comctl32.dll | Cette fonction permet d'effectuer la création d'une barre d'état dans le bas d'une fenêtre d'application
CreateTapePartition | kernel32.dll | Cette fonction permet de reformatée une unité de sauvegarde de bande magnétique (Tape Backup).
CreateToolhelp32Snapshot | kernel32.dll | Cette fonction permet d'effectuer une capture de l'écran sous forme d'un Bitmap
CreateThread | kernel32.dll | Cette fonction permet de créer un processus pour exécuter un processus appelant dans un espace d'adressage.
CreateWaitableTimerA | kernel32.dll | Cette fonction permet d'effectuer la création d'une horloge de délai d'attente.
CreateWaitableTimerW | kernel32.dll | Cette fonction permet d'effectuer la création d'une horloge de délai d'attente avec des paramètres de chaine de caractères étendue (Unicode).
CreateWindowExA | user32.dll | Cette fonction permet d'effectuer la création d'une sous-fenêtre (PopUp).
CreateWindowExW | user32.dll | Cette fonction permet d'effectuer la création d'une sous-fenêtre (PopUp) avec des paramètres de chaine de caractères étendues (Unicode).
CreateWindowStationA | user32.dll | Cette fonction permet d'effectuer la création d'un objet de fenêtre de station.
CreateWindowStationW | user32.dll | Cette fonction permet d'effectuer la création d'un objet de fenêtre de station avec des paramètres de chaine de caractères étendues (Unicode).
DdeSetQualityOfService | user32.dll | Cette fonction permet de spécifier le service de qualité (QoS) des applications d'échanges de données dynamique brute (DDE) désiré pour de future initialisation de conversion DDE.
DebugActiveProcess | kernel32.dll | Cette fonction permet d'allouer une débogueur à attaché avec un processus actif
DebugBreak | kernel32.dll | Cette fonction permet de provoqué une exception sur un point d'arrêt dans le processus courant.
DefDlgProcA | user32.dll | Cette fonction permet d'effectuer le traitement de processus de fenêtre de message par défaut.
DefDlgProcW | user32.dll | Cette fonction permet d'effectuer le traitement de processus de fenêtre de message par défaut avec des paramètres de chaine de caractères étendues (Unicode).
DeferWindowPos | user32.dll | Cette fonction permet de mettre à jour la structure de positionnement d'une fenêtre.
DefFrameProcA | user32.dll | Cette fonction permet d'effectuer le processus de message par défaut du cadre d'une fenêtre MDI.
DefFrameProcW | user32.dll | Cette fonction permet d'effectuer le processus de message par défaut du cadre d'une fenêtre MDI avec des paramètres de chaine de caractères étendues (Unicode).
DefineDosDeviceA | kernel32.dll | Cette fonction permet de définir, redéfinir ou supprimer un nom de périphérique DOS.
DefineDosDeviceW | kernel32.dll | Cette fonction permet de définir, redéfinir ou supprimer un nom de périphérique DOS avec des paramètres de chaine de caractères étendue (Unicode).
DefMDIChildProcA | user32.dll | Cette fonction permet de définir le processus de message de la fenêtre enfant MDI.
DefMDIChildProcW | user32.dll | Cette fonction permet de définir le processus de message de la fenêtre enfant MDI avec des paramètres de chaine de caractères étendues (Unicode).
DefWindowProcA | user32.dll | Cette fonction permet de créer un événement produit par défaut lorsqu'une action est effectuer dans la fenêtre.
DefWindowProcW | user32.dll | Cette fonction permet de créer un événement produit par défaut lorsqu'une action est effectué dans la fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
DeleteAce | advapi32.dll | Cette fonction permet d'effacer une entrée de contrôle d'accès (ACE) d'une liste de contrôle d'accès (ACL).
DeleteAtom | kernel32.dll | Cette fonction permet de décrémenter le compteur de référence de chaine de caractères d'Atom local.
DeleteColorSpace | gdi32.dll | Cette fonction permet de supprimer l'espace de couleur spécifié et libérer tous les ressources internes associé avec celui-ci.
DeleteCriticalSection | kernel32.dll | Cette fonction permet de terminer et restituer tous les ressources associé à l'objet de section critique.
DeleteDC | gdi32.dll | Cette fonction permet d'effectuer la suppression d'un périphérique de contexte (DC).
DeleteEnhMetaFile | gdi32.dll | Cette fonction permet de supprimer le format étendue du fichier méta ou le Handle du fichier méta de format étendue.
DeleteFiber | kernel32.dll | Cette fonction permet de supprimer une fibre existante.
DeleteFileA | kernel32.dll | Cette fonction permet de supprimer un fichier existant.
DeleteFileW | kernel32.dll | Cette fonction permet de supprimer un fichier existant avec des paramètres de chaine de caractères étendue (Unicode).
DeleteMenu | user32.dll | Cette fonction permet de supprimer un item de menu.
DeleteMetaFile | gdi32.dll | Cette fonction permet de supprimer un fichier de méta de format Windows ou le Handle d'un fichier méta de format Windows.
DeleteObject | gdi32.dll | Cette fonction permet de supprimer un crayon, une brosse, une police de caractères, un Bitmap ou une palette logique et d'ainsi libérer les ressources systèmes associé avec cette objet.
DeletePrinter | winspool.drv | Cette fonction permet de supprimer un objet d'imprimante.
DeregisterEventSource | advapi32.dll | Cette fonction permet de fermer un identificateur Handle d'enregistrement d'événement retourner par une fonction «RegisterEventSource».
DescribePixelFormat | gdi32.dll | Cette fonction permet de demander les informations à propos du format d'un pixel spécifié d'un périphérique spécifié.
DestroyAcceleratorTable | user32.dll | Cette fonction permet de détruire la table d'accélérateur.
DestroyCaret | user32.dll | Cette fonction permet de supprimer le caret courant.
DestroyCursor | user32.dll | Cette fonction permet de supprimer une image de pointeur de souris
DestroyIcon | user32.dll | Cette fonction permet de supprimer une icône
DestroyMenu | user32.dll | Cette fonction permet de supprimer un menu contextuel
DestroyPrivateObjectSecurity | advapi32.dll | Cette fonction permet d'effacer un descripteur de sécurité d'un objet serveur protégé.
DestroyWindow | user32.dll | Cette fonction permet de supprimer une fenêtre
DeviceCapabilitiesExA | gdi32.dll | Cette fonction permet de demander la compatibilité pour le pilote d'imprimante spécifié.
DeviceCapabilitiesExW | gdi32.dll | Cette fonction permet de demander la compatibilité pour le pilote d'imprimante spécifié avec des paramètres de chaine de caractères étendues (Unicode).
DeviceIoControl | kernel32.dll | Cette fonction permet d'envoyer des codes de contrôles directement à un pilote de périphérique.
DialogBoxIndirectParamA | user32.dll | Cette fonction permet de créer une boite de dialogue modèle à l'aide d'un gabarit en mémoire et des paramètres spécifiés.
DialogBoxIndirectParamW | user32.dll | Cette fonction permet de créer une boite de dialogue modèle à l'aide d'un gabarit en mémoire et des paramètres spécifiés avec des paramètres de chaine de caractères étendues (Unicode).
DialogBoxParamA | user32.dll | Cette fonction permet de créer une boite de dialogue modèle et des paramètres spécifiés.
DialogBoxParamW | user32.dll | Cette fonction permet de créer une boite de dialogue modèle et des paramètres spécifiés avec des paramètres de chaine de caractères étendues (Unicode).
DisableThreadLibraryCalls | kernel32.dll | Cette fonction permet de désactiver les avertissements «DLL_THREAD_ATTACH» et «DLL_THREAD_DETACH» des bibliothèques de lien dynamique (DLL) spécifié.
DisconnectNamedPipe | kernel32.dll | Cette fonction permet de déconnecter le serveur de fin de tuyau nommé pour un processus client.
DiskCopyRunDll | diskcopy.dll | Cette fonction permet de lancer une boite de dialogue offrant la possibilité d'effectuer une copie d'unité de disquette
DispatchMessageA | user32.dll | Cette fonction permet de redistribuer le messages de fenêtre.
DispatchMessageW | user32.dll | Cette fonction permet de redistribuer le messages de fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
DlgDirListA | user32.dll | Cette fonction permet de remplir une boite de dialogue avec une liste de fichiers et de répertoires.
DlgDirListW | user32.dll | Cette fonction permet de remplir une boite de dialogue avec une liste de fichiers et de répertoires avec des paramètres de chaine de caractères étendues (Unicode).
DlgDirListComboBoxA | user32.dll | Cette fonction permet de remplir une boite combinée (Combo Box) avec une liste de fichiers et de répertoires.
DlgDirListComboBoxW | user32.dll | Cette fonction permet de remplir une boite combinée (Combo Box) avec une liste de fichiers et de répertoires avec des paramètres de chaine de caractères étendues (Unicode).
DlgDirSelectComboBoxExA | user32.dll | Cette fonction permet de demander la sélection courante d'une boite combinée remplit par la fonction DlgDirListComboBox.
DlgDirSelectComboBoxExW | user32.dll | Cette fonction permet de demander la sélection courante d'une boite combinée remplit par la fonction DlgDirListComboBox avec des paramètres de chaine de caractères étendues (Unicode).
DlgDirSelectExA | user32.dll | Cette fonction permet de demander la sélection courante étendue d'une liste de boite.
DlgDirSelectExW | user32.dll | Cette fonction permet de demander la sélection courante étendue d'une liste de boite avec des paramètres de chaine de caractères étendues (Unicode).
DllGetVersion | shell32.dll | Cette fonction permet de demander les informations de la version de DLL
DosDateTimeToFileTime | kernel32.dll | Cette fonction permet de convertir la valeur d'une date et d'une heure du DOS en un temps fichier de 64 bits.
DPtoLP | gdi32.dll | Cette fonction permet d'effectuer la conversion des coordonnées d'un périphérique en coordonnées logique.
DragAcceptFiles | shell32.dll | Cette fonction permet de fixer l'état du drapeau indiquant si la fenêtre accepte les fichiers glissées
DragDetect | user32.dll | Cette fonction permet de déterminer quand une fonction de glisser-déposer (drag and drop) est mis en place.
DragObject | user32.dll | Cette fonction permet d'intéger une composante serveur de glisser-déposer (drag and drop).
DragFinish | shell32.dll | Cette fonction permet de restituer la mémoire employé pour le transfert des noms de fichiers vers une fenêtre d'application
DragQueryFile | shell32.dll | Cette fonction permet de demander les noms de fichiers lors d'un transfert des noms de fichiers vers une fenêtre d'application
DrawAnimatedRects | user32.dll | Cette fonction permet de dessiner un cadre de rectangle et d'animer l'icône ouvert.
DrawCaption | user32.dll | Cette fonction permet de dessiner une fenêtre de légende.
DrawEdge | user32.dll | Cette fonction permet de dessiner une ou plusieurs bordures d'un rectangle.
DrawEscape | gdi32.dll | Cette fonction permet d'accéder à la compatibilité de dessinage de l'affichage d'un vidéo n'étant pas directement disponible par le GDI (Graphics Device Interface).
DrawFocusRect | user32.dll | Cette fonction permet de dessiner un rectangle avec un style indiquant qu'il est focusé.
DrawFrameControl | user32.dll | Cette fonction permet de dessiner un cadre de contrôle pour le type et le style spécifié.
DrawIcon | user32.dll | Cette fonction permet d'afficher une icône dans la fenêtre d'application du contexte de l'objet
DrawIconEx | user32.dll | Cette fonction permet d'afficher un icône ou un curseur dans une région client d'une fenêtre d'un périphérique de contexte donnée, d'effectuer des opérations de traitement de trame spécifié et d'étirement ou de compression de l'icône ou le curseur spécifié.
DrawMenuBar | user32.dll | Cette fonction permet de réafficher la barre de menu de la fenêtre d'application.
DrawStateA | user32.dll | Cette fonction permet d'afficher une image et d'appliquer un effet visuel indiquant son état, laquelle peut être désactiver ou un état par défaut.
DrawStateW | user32.dll | Cette fonction permet d'afficher une image et d'appliquer un effet visuel indiquant son état, laquelle peut être désactiver ou un état par défaut avec des paramètres de chaine de caractères étendues (Unicode).
DrawTextA | user32.dll | Cette fonction permet de dessiner le texte formaté dans le rectangle spécifié.
DrawTextW | user32.dll | Cette fonction permet de dessiner le texte formaté dans le rectangle spécifié avec des paramètres de chaine de caractères étendues (Unicode).
DrawTextExA | user32.dll | Cette fonction permet de dessiner le texte formaté dans le rectangle spécifié avec des paramètres supplémentaires.
DrawTextExW | user32.dll | Cette fonction permet de dessiner le texte formaté dans le rectangle spécifié avec des paramètres supplémentaires et des chaines de caractères étendues (Unicode).
DuplicateHandle | kernel32.dll | Cette fonction permet de dupliquer un objet d'identificateur Handle existant.
DuplicateIcon | shell32.dll | Cette fonction permet d'effectuer la création d'un nouvel icône identique à une autre icône spécifié.
DuplicateToken | advapi32.dll | Cette fonction permet de créer un nouvel accès de jeton dupliqué sur un autre jeton déjà existant.
DuplicateTokenEx | advapi32.dll | Cette fonction permet d'effectuer la conversion d'un jeton spécifié par un jeton principal.
DvdLauncher | storprop.dll | Cette fonction permet de vérifier si la région que contient le DVD correspond avec celle du système d'exploitation.
Ellipse | gdi32.dll | Cette fonction permet d'effectuer l'afficher d'une ellipse (forme d'ovale).
EmptyClipboard | user32.dll | Cette fonction permet de vider complètement le tampon du presse-papier.
EnableMenuItem | user32.dll | Cette fonction permet d'activer ou de désactiver un item de menu déroulant.
EnableScrollBar | user32.dll | Cette fonction permet d'activer ou de désactiver les barres de défilement dans une fenêtre d'application.
EnableWindow | user32.dll | Cette fonction permet d'activer ou de désactiver les événements clavier et souris et les contrôles dans une fenêtre d'application.
EndDeferWindowPos | user32.dll | Cette fonction permet d'effectuer la simulation de mise-à-jour de position et de taille d'une ou plusieurs fenêtres dans un simple cycle de rafraichissement d'écran.
EndDialog | user32.dll | Cette fonction permet de cacher un modèle de boite de dialogue.
EndDoc | gdi32.dll | Cette fonction permet d'indiquer que l'application a terminé d'envoyer des données au tampon d'impression de l'imprimante.
EndPage | gdi32.dll | Cette fonction permet d'indiquer que l'application a terminé d'envoyer une page de données au tampon d'impression de l'imprimante.
EndPaint | user32.dll | Cette fonction permet de marquer la fin du traitement de peinture d'une fenêtre spécifié.
EndPath | gdi32.dll | Cette fonction permet de fermer une braquette de chemin et sélectionne le chemin définit par la braquette dans le périphérique de contexte (DC) spécifié.
EndUpdateResourceA | kernel32.dll | Cette fonction permet de terminer la mise-à-jour d'une ressource d'un fichier exécutable.
EndUpdateResourceW | kernel32.dll | Cette fonction permet de terminer la mise-à-jour d'une ressource d'un fichier exécutable avec des paramètres de chaine de caractères étendue (Unicode).
EnterCriticalSection | kernel32.dll | Cette fonction permet d'attendre après un propriétaire d'objet de section critique spécifié.
EnumCalendarInfoA | kernel32.dll | Cette fonction permet d'énumérer des informations de calendrier d'une région local spécifié
EnumCalendarInfoW | kernel32.dll | Cette fonction permet d'énumérer des informations de calendrier d'une région local spécifié avec des paramètres de chaine de caractères étendue (Unicode).
EnumChildWindows | user32.dll | Cette fonction permet d'énumérer les fenêtres enfants de la fenêtre parent en passant le Handle de chaque fenêtre enfant avec une fonction d'application utilisateur.
EnumClipboardFormats | user32.dll | Cette fonction permet de retourner les formats de presse-papier disponibles.
EnumDateFormatsA | kernel32.dll | Cette fonction permet d'énumérer le format de la date courte ou longue disponible pour la région locale.
EnumDateFormatsW | kernel32.dll | Cette fonction permet d'énumérer le format de la date courte ou longue disponible pour la région locale avec des paramètres de chaine de caractères étendue (Unicode).
EnumDesktopsA | user32.dll | Cette fonction permet de demander l'énumération de tous les bureaux d'une fenêtre de travail associé avec un processus appelant.
EnumDesktopsW | user32.dll | Cette fonction permet de demander l'énumération de tous les bureaux d'une fenêtre de travail associé avec un processus appelant avec des paramètres de chaine de caractères étendues (Unicode).
EnumDesktopWindows | user32.dll | Cette fonction permet de demander l'énumération de tous les bureaux passé par le Handle de chaque fenêtre, par l'entremise d'une fonction utilisateur.
EnumDisplaySettingsA | user32.dll | Cette fonction permet de demander des informations sur le système d'affichage.
EnumDisplaySettingsW | user32.dll | Cette fonction permet de demander des informations sur le système d'affichage avec des paramètres de chaine de caractères étendues (Unicode).
EnumEnhMetaFile | gdi32.dll | Cette fonction permet d'énumérer les enregistrements d'un format étendue d'un méta fichier afin de demander chaques enregistrements et de les passés par une fonction utilisateur.
EnumFontFamiliesA | gdi32.dll | Cette fonction permet d'énumérer les polices de caractères d'une famille de police de caractères spécifié étant disponible pour un périphérique spécifié.
EnumFontFamiliesW | gdi32.dll | Cette fonction permet d'énumérer les polices de caractères d'une famille de police de caractères spécifié étant disponible pour un périphérique spécifié avec des paramètres de chaine de caractères étendues (Unicode).
EnumFontFamiliesExA | gdi32.dll | Cette fonction permet d'énumérer toutes les polices de caractères système correspondant aux caractéristiques de polices de caractères spécifié par une structure.
EnumFontFamiliesExW | gdi32.dll | Cette fonction permet d'énumérer toutes les polices de caractères système correspondant aux caractéristiques de polices de caractères spécifié par une structure avec des paramètres de chaine de caractères étendues (Unicode).
EnumFontsA | gdi32.dll | Cette fonction permet d'énumérer les polices de caractères disponibles pour le périphérique spécifié.
EnumFontsW | gdi32.dll | Cette fonction permet d'énumérer les polices de caractères disponibles pour le périphérique spécifié avec des paramètres de chaine de caractères étendues (Unicode).
EnumICMProfilesA | gdi32.dll | Cette fonction permet d'énumérer les différents profiles de couleurs étant supporté par le système pour le périphérique de contexte spécifié.
EnumICMProfilesW | gdi32.dll | Cette fonction permet d'énumérer les différents profiles de couleurs étant supporté par le système pour le périphérique de contexte spécifié avec des paramètres de chaine de caractères étendues (Unicode).
EnumJobsA | winspool.drv | Cette fonction permet de définir les travaux d'impressions d'une imprimante.
EnumMetaFile | gdi32.dll | Cette fonction permet de demander d'énumérer les enregistrements de fichier méta de format Windows en utilisant une fonction d'application utilisateur.
EnumObjects | gdi32.dll | Cette fonction permet d'énumérer les crayons et les brosses disponibles pour le périphérique de contexte spécifié.
EnumPortsA | winspool.drv | Cette fonction permet de demander les ports d'impressions d'imprimante disponible.
EnumPrintersA | winspool.drv | Cette fonction permet de demander des informations sur une imprimante.
EnumPropsA | user32.dll | Cette fonction permet d'effectuer le passage de la liste des entrées de propriétés par l'intermédiaire d'une fonction utilisateur.
EnumPropsExA | user32.dll | Cette fonction permet d'effectuer le passage de la liste des entrées de propriétés par l'intermédiaire d'une fonction utilisateur avec des paramètres supplémentaires.
EnumPropsExW | user32.dll | Cette fonction permet d'effectuer le passage de la liste des entrées de propriétés par l'intermédiaire d'une fonction utilisateur avec des paramètres supplémentaires et des chaines de caractères étendues (Unicode).
EnumPropsW | user32.dll | Cette fonction permet d'effectuer le passage de la liste des entrées de propriétés par l'intermédiaire d'une fonction utilisateur avec des paramètres de chaine de caractères étendues (Unicode).
EnumResourceLanguagesA | kernel32.dll | Cette fonction permet de rechercher un module pour chaque ressource d'un type spécifié et de passé le langage pour chaque ressource trouvé dans une fonction définie en arrière plan.
EnumResourceLanguagesW | kernel32.dll | Cette fonction permet de rechercher un module pour chaque ressource d'un type spécifié et de passé le langage pour chaque ressource trouvé dans une fonction définie en arrière plan avec des paramètres de chaine de caractères étendue (Unicode).
EnumResourceNamesA | kernel32.dll | Cette fonction permet de rechercher un module pour chaque ressource d'un type spécifié et de passé le langage pour chaque ressource trouvé dans une fonction d'application définie en arrière plan.
EnumResourceNamesW | kernel32.dll | Cette fonction permet de rechercher un module pour chaque ressource d'un type spécifié et de passé le langage pour chaque ressource trouvé dans une fonction d'application définie en arrière plan avec des paramètres de chaine de caractères étendue (Unicode).
EnumResourceTypesA | kernel32.dll | Cette fonction permet de rechercher un module pour une ressource et de passé chaque type de ressource dans une fonction d'application définie en arrière plan.
EnumResourceTypesW | kernel32.dll | Cette fonction permet de rechercher un module pour une ressource et de passé chaque type de ressource dans une fonction d'application définie en arrière plan avec des paramètres de chaine de caractères étendue (Unicode).
EnumSystemCodePagesA | kernel32.dll | Cette fonction permet d'énumérer un code de page installé ou supporté par le système d'exploitation.
EnumSystemCodePagesW | kernel32.dll | Cette fonction permet d'énumérer un code de page installé ou supporté par le système d'exploitation avec des paramètres de chaine de caractères étendue (Unicode).
EnumSystemLocalesA | kernel32.dll | Cette fonction permet d'énumérer la région locale installé ou supporté par le système d'exploitation.
EnumSystemLocalesW | kernel32.dll | Cette fonction permet d'énumérer la région locale installé ou supporté par le système d'exploitation avec des paramètres de chaine de caractères étendue (Unicode).
EnumTimeFormatsA | kernel32.dll | Cette fonction permet d'énumérer le format de temps disponible pour la région locale.
EnumTimeFormatsW | kernel32.dll | Cette fonction permet d'énumérer le format de temps disponible pour la région locale avec des paramètres de chaine de caractères étendue (Unicode).
EnumThreadWindows | user32.dll | Cette fonction permet d'effectuer l'énumération de toutes les fenêtres non-enfant associé avec un processus par passage d'un Handle pour chaque fenêtre avec une fonction d'application utilisateur.
EnumWindowStationsA | user32.dll | Cette fonction permet d'effectuer l'énumération de toutes les fenêtres de station dans le système par passage du nom pour chaque fenêtre de station avec une fonction d'application utilisateur.
EnumWindowStationsW | user32.dll | Cette fonction permet d'effectuer l'énumération de toutes les fenêtres de station dans le système par passage du nom pour chaque fenêtre de station avec une fonction d'application utilisateur et des paramètres de chaine de caractères étendues (Unicode).
EnumWindows | user32.dll | Cette fonction permet d'effectuer le passage des fenêtres parent par l'intermédiaire d'une fonction utilisateur.
EqualPrefixSid | advapi32.dll | Cette fonction permet de vérifie sur les valeurs de deux préfixes SID (identificateur de sécurité) sont égales à l'exception de la dernière valeur de sous-autorité.
EqualRect | user32.dll | Cette fonction permet de demander lequel des deux rectangles spécifiés sont égales par comparaison des coordonnées de leur coin supérieur gauche et de leur coin inférieur droit.
EqualRgn | gdi32.dll | Cette fonction permet de demander lequel des deux régions spécifiés sont égales par comparaison des coordonnées de leur coin supérieur gauche et de leur coin inférieur droit.
EqualSid | advapi32.dll | Cette fonction permet de vérifie sur les valeurs de deux préfixes SID (identificateur de sécurité) sont égales.
EraseTape | kernel32.dll | Cette fonction permet de tout supprimer ou supprimer des parties de bande magnétique de sauvegarde.
Escape | gdi32.dll | Cette fonction permet d'activer les applications de compatibilité d'accès d'un périphérique particulier non accessible directement par le GDI.
EscapeCommFunction | kernel32.dll | Cette fonction permet d'effectuer directement une communication d'un périphérique spécifié à une fonction étendue.
ExcludeClipRect | gdi32.dll | Cette fonction permet de créer une nouvelle région de Clip consistant en une minuscule région existante du rectangle spécifié.
ExcludeUpdateRgn | user32.dll | Cette fonction permet d'exclure la région mise-à-jour d'une région de capture.
ExitProcess | kernel32.dll | Cette fonction permet de terminer un processus et tous ses processus légers.
ExitThread | kernel32.dll | Cette fonction permet de terminer un processus léger.
ExitWindowsEx | user32.dll | Cette fonction permet d'effectuer l'arrêt ou le redémarrage de Windows et/ou de l'ordinateur.
ExpandEnvironmentStringsA | kernel32.dll | Cette fonction permet d'étendre une chaine de caractères de variable d'environnement et de remplacer leurs valeurs par celle correspondante.
ExpandEnvironmentStringsW | kernel32.dll | Cette fonction permet d'étendre une chaine de caractères de variable d'environnement et de remplacer leurs valeurs par celle correspondante avec des paramètres de chaine de caractères étendue (Unicode).
ExtCreatePen | gdi32.dll | Cette focntion permet de créer un crayon cosmétique ou géométrique logique ayant les attributs de style, de largeur et brosses spécifiés.
ExtCreateRegion | gdi32.dll | Cette fonction permet de créer une région avec la région et la transformation de données spécifié.
ExtEscape | gdi32.dll | Cette fonction permet d'allouer les applications vers une capacité d'accès d'un périphérique particulier n'étant pas disponible pour le GDI.
ExtFloodFill | gdi32.dll | Cette fonction permet de remplir une région d'une surface d'affichage avec la brosse courante.
ExtractAssociatedIconA | shell32.dll | Cette fonction permet de demander le Handle dans icône indexé dans un fichier.
ExtractIconA | shell32.dll | Cette fonction permet de demander une unique icône dans un fichier.
ExtractIconExA | shell32.dll | Cette fonction permet de demander plusieurs icônes dans un fichier.
ExtSelectClipRgn | gdi32.dll | Cette fonction permet de combiner la région spécifié avec la région de capture courante en utilisant le mode spécifié.
ExtTextOutA | gdi32.dll | Cette fonction permet de dessiner une chaine de caractères en utilisant la police de caractères courante.
ExtTextOutW | gdi32.dll | Cette fonction permet de dessiner une chaine de caractères en utilisant la police de caractères courante avec des paramètres de chaines de caractères étendues (Unicode).
FatalAppExitA | kernel32.dll | Cette fonction permet d'afficher la boite de dialogue d'erreur fatal amenant la terminaison anormale du programme.
FatalAppExitW | kernel32.dll | Cette fonction permet d'afficher la boite de dialogue d'erreur fatal amenant la terminaison anormale du programme avec des paramètres de chaine de caractères étendue (Unicode).
FatalExit | kernel32.dll | Cette fonction permet de transférer le contrôle d'exécution au débogueur.
FileTimeToDosDateTime | kernel32.dll | Cette fonction permet de convertir un temps de fichier 64 bits en des valeurs de date et temps.
FileTimeToLocalFileTime | kernel32.dll | Cette fonction permet d'effectuer la conversion du temps d'un fichier de format UTC (Coordonnée de Temps Universelle) en format de temps local.
FileTimeToSystemTime | kernel32.dll | Cette fonction permet de convertir un temps fichier de 64 bits à un temps de format système.
FillConsoleOutputAttribute | kernel32.dll | Cette fonction permet de fixer la couleur de texte et d'arrière plan pour un nombre spécifié de caractères en commençant au coordonnées du tampon d'écran spécifié.
FillConsoleOutputCharacterA | kernel32.dll | Cette fonction permet d'écrire un caractère dans le tampon d'écran un certain nombre de fois en commençant au coordonnées du tampon d'écran spécifié.
FillConsoleOutputCharacterW | kernel32.dll | Cette fonction permet d'écrire un caractère dans le tampon d'écran un certain nombre de fois en commençant au coordonnées du tampon d'écran spécifié avec des paramètres de chaine de caractères étendue (Unicode).
FillPath | gdi32.dll | Cette fonction permet de fermer n'importe quel figures ouvertes dans le chemin courant et remplis l'intérieur du chemin en utilisant la brosses courante et le mode de remplissage de polygone.
FillRect | user32.dll | Cette fonction permet de remplir un rectangle avec la brosse spécifié.
FillRgn | gdi32.dll | Cette fonction permet de remplir la région avec le brosse spécifié.
FindAtomA | kernel32.dll | Cette fonction permet de rechercher dans un table d'Atom local la chaine de caractères spécifié et demande l'Atom associé avec cette chaine de caractères.
FindAtomW | kernel32.dll | Cette fonction permet de rechercher dans un table d'Atom local la chaine de caractères spécifié et demande l'Atom associé avec cette chaine de caractères avec des paramètres de chaine de caractères étendue (Unicode).
FindClose | kernel32.dll | Cette fonction permet d'indiquer que la recherche des fichiers et réperoires que contient un répertoire est terminé.
FindCloseChangeNotification | kernel32.dll | Cette fonction permet d'indiquer que la recherche et modifications des fichiers et répertoires que contient un répertoire est terminé.
FindExecutableA | shell32.dll | Cette fonction permet de demander le fichier devant être exécuté lorsque la sorte d'extension de fichier est rencontré.
FindFirstChangeNotificationA | kernel32.dll | Cette fonction permet de créer un identificateur Handle d'avertissement de changement et fixe la condition d'avertissement de changement initial.
FindFirstChangeNotificationW | kernel32.dll | Cette fonction permet de créer un identificateur Handle d'avertissement de changement et fixe la condition d'avertissement de changement initial avec des paramètres de chaine de caractères étendue (Unicode).
FindFirstChangeNotification | kernel32.dll | Cette fonction permet de spécifier une recherche et modifications des fichiers et répertoires que contient un répertoire.
FindFirstFileA | kernel32.dll | Cette fonction permet de spécifier une recherche des fichiers et répertoires que contient un répertoire.
FindFirstFileExA | kernel32.dll | Cette fonction permet de rechercher dans un répertoire un fichier ou un sous-répertoire avec une correspondance de nom et d'attribut spécifié.
FindFirstFileExW | kernel32.dll | Cette fonction permet de rechercher dans un répertoire un fichier ou un sous-répertoire avec une correspondance de nom et d'attribut spécifié avec des paramètres de chaine de caractères étendue (Unicode).
FindFirstFileW | kernel32.dll | Cette fonction permet de spécifier une recherche des fichiers et répertoires que contient un répertoire avec des paramètres de chaine de caractères étendue (Unicode).
FindFirstFreeAce | advapi32.dll | Cette fonction permet de retourner un pointeur sur le premier octet de libre sur une liste de contrôle d'accès (ACL).
FindNextChangeNotification | kernel32.dll | Cette fonction permet demander l'entrée suivant lors de la recherche et modifications des fichiers et répertoire que contient un répertoire.
FindNextFileA | kernel32.dll | Cette fonction permet demander l'entrée suivante lors de la recherche des fichiers et répertoires que contient un répertoire.
FindNextFileW | kernel32.dll | Cette fonction permet demander l'entrée suivante lors de la recherche des fichiers et répertoire que contient un répertoire avec des paramètres de chaine de caractères étendue (Unicode).
FindResourceA | kernel32.dll | Cette fonction permet de demander l'emplacement d'une ressource avec un nom et un type spécifié dans un module spécifié.
FindResourceW | kernel32.dll | Cette fonction permet de demander l'emplacement d'une ressource avec un nom et un type spécifié dans un module spécifié avec des paramètres de chaine de caractères étendue (Unicode).
FindResourceExA | kernel32.dll | Cette fonction permet de demander l'emplacement d'une ressource avec un type, un nom et une langue spécifié dans un module spécifié.
FindResourceExW | kernel32.dll | Cette fonction permet de demander l'emplacement d'une ressource avec un type, un nom et une langue spécifié dans un module spécifié avec des paramètres de chaine de caractères étendue (Unicode).
FindWindowA | user32.dll | Cette fonction permet de demander le Handle de la première fenêtre d'application correspondant au nom de la fenêtre.
FindWindowExA | user32.dll | Cette fonction permet de demander le Handle d'une fenêtre enfant d'une fenêtre d'application.
FindWindowW | user32.dll | Cette fonction permet de demander le Handle de la première fenêtre d'application correspondant au nom de la fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
FlashWindow | user32.dll | Cette fonction permet de faire clignoter une fois une fenêtre d'application.
FlashWindowEx | user32.dll | Cette fonction permet de faire clignoter plusieurs fois une fenêtre d'application.
FlattenPath | gdi32.dll | Cette fonction permet de transformer n'importe quel courbe d'un chemin étant sélectionné dans le périphérique de contexte courant de façon à tourner chaque courbe une séquences de lignes.
FloodFill | gdi32.dll | Cette fonction permet d'effectuer le remplissage d'une région de la surface de l'écran en utilisant la brosse courante.
FlushConsoleInputBuffer | kernel32.dll | Cette fonction permet de vider le tampon d'entrée de la console.
FlushFileBuffers | kernel32.dll | Cette fonction permet d'effacer le tampon pour le fichier spécifié et provoquer l'écriture des données dans le fichier.
FlushInstructionCache | kernel32.dll | Cette fonction permet de vider les instructions en cache pour le processus spécifié.
FlushViewOfFile | kernel32.dll | Cette fonction permet d'écrire sur le disque un rang d'octets dans une vue d'un fichier.
FoldStringA | kernel32.dll | Cette fonction permet de cartographier une chaine de caractères vers une autre en effectuant les options de transformations spécifié.
FoldStringW | kernel32.dll | Cette fonction permet de cartographier une chaine de caractères vers une autre en effectuant les options de transformations spécifié avec des paramètres de chaine de caractères étendue (Unicode).
FormatMessageA | kernel32.dll | Cette fonction permet de demander un message affecté par un certain format.
FormatMessageW | kernel32.dll | Cette fonction permet de demander le dernier message d'erreur contenu dans le fichier d'erreur système.
FrameRect | user32.dll | Cette fonction permet de dessiner une bordure autour d'un rectangle, en utilisant la brosse spécifié.
FrameRgn | gdi32.dll | Cette fonction permet de dessiner une bordure entourant une région spécifié et en utilisant la brosse spécifié.
FreeConsole | kernel32.dll | Cette fonction permet de libérer une console existante.
FreeDDElParam | user32.dll | Cette fonction permet de libérer la mémoire spécifié d'un message DDE posté.
FreeEnvironmentStringsA | kernel32.dll | Cette fonction permet de libérer un bloc de chaine de caractères d'environnement.
FreeEnvironmentStringsW | kernel32.dll | Cette fonction permet de libérer un bloc de chaine de caractères d'environnement avec des paramètres de chaine de caractères étendue (Unicode).
FreeLibrary | kernel32.dll | Cette fonction permet d'incrémenter un compteur de référence pour un chargement de module de lien dynamique (DLL).
FreeLibraryAndExitThread | kernel32.dll | Cette fonction permet de décrémenté le compteur de référence d'une des bibliothèque de liens dynamique (DLL) et appel alors la fonction «ExitThread».
FreeResource | kernel32.dll | Cette fonction permet de libérer une ressource.
FreeSid | advapi32.dll | Cette fonction permet de libérer un identificateur de sécurité (SID) précédemment alloué par la fonction «AllocateAndInitializeSid».
FtpCreateDirectoryA | wininet.dll | Cette fonction permet d'effectuer la création d'un répertoire sur un protocole FTP.
FtpOpenFileA | wininet.dll | Cette fonction permet d'ouvrir un fichier avec une chaine de caractères ASCIZ sur un protocole FTP.
FtpOpenFileW | wininet.dll | Cette fonction permet d'ouvrir un fichier avec une chaine de caractères étendue (Unicode) sur un protocole FTP.
GdiComment | gdi32.dll | Cette fonction permet de copier un commentaire d'un tampon vers un méta fichier spécifié de format étendue.
GdiFlush | gdi32.dll | Cette fonction permet de vider le traitement en lot courant du processus appelant.
GdiGetBatchLimit | gdi32.dll | Cette fonction permet de retourner le nombre maximum d'appels de fonction étant accumulé dans le traitement par lot courant du processus léger courant.
GdiSetBatchLimit | gdi32.dll | Cette fonction permet de fixer le nombre maximum d'appels de fonction étant accumulé dans le traitement par lot courant du processus léger courant.
GenerateConsoleCtrlEvent | kernel32.dll | Cette fonction permet d'envoyer un signal spécifique à un groupe de processus de console.
GetAce | advapi32.dll | Cette fonction permet de demander un pointeur sur une structure d'une entrée de contrôle d'accès (ACE) d'une liste de contrôle d'accès (ACL).
GetAclInformation | advapi32.dll | Cette fonction permet de demander les informations concernant une liste de contrôle d'accès (ACL).
GetACP | kernel32.dll | Cette fonction permet de demander l'identificateur de code de page ANSI courant pour le système d'exploitation.
GetActiveWindow | user32.dll | Cette fonction permet de demander le Handle de la fenêtre active.
GetArcDirection | gdi32.dll | Cette fonction permet de demander la direction de l'arc courant pour le périphérique de contexte spécifié.
GetAspectRatioFilterEx | gdi32.dll | Cette fonction permet de demander l'ajustement de filtre d'aspect ratio courant.
GetAsyncKeyState | user32.dll | Cette fonction permet de retourner l'état de la touche spécifié.
GetAtomNameA | kernel32.dll | Cette fonction permet de demander une copie de chaine de caractères associé avec un Atom local spécifié.
GetAtomNameW | kernel32.dll | Cette fonction permet de demander une copie de chaine de caractères associé avec un Atom local spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetBinaryTypeA | kernel32.dll | Cette fonction permet de demander si le fichier est exécutable, et s'il l'est, quel type de fichier exécutable s'agit-il.
GetBinaryTypeW | kernel32.dll | Cette fonction permet de demander si le fichier est exécutable, et s'il l'est, quel type de fichier exécutable s'agit-il avec des paramètres de chaine de caractères étendue (Unicode).
GetBitmapBits | gdi32.dll | Cette fonction permet de copier les bits du Bitmap spécifié dans un tampon.
GetBitmapDimensionEx | gdi32.dll | Cette fonction permet de demander les dimensions d'un Bitmap.
GetBkColor | gdi32.dll | Cette fonction permet de retourner la couleur d'arrière-plan courante.
GetBkMode | gdi32.dll | Cette fonction permet de demander le mode mixte d'arrière-plan pour le contexte de périphérique spécifié.
GetBoundsRect | gdi32.dll | Cette fonction permet de retourner l'accumulation d'informations de limite rectangulaire d'un périphérique de contexte.
GetBrushOrgEx | gdi32.dll | Cette fonction permet de demander l'origine de la brosse courante dans un périphérique de contexte courant.
GetCapture | user32.dll | Cette fonction permet de demander le Handle de la fenêtre où la souris est capturé.
GetCaretBlinkTime | user32.dll | Cette fonction permet de demander le taux de clignotement du caret.
GetCaretPos | user32.dll | Cette fonction permet de demander la position courante d'un caret.
GetCharABCWidthsA | gdi32.dll | Cette fonction permet de demander la largeur, dans son unité de mesure logique, de caractères consécutifs dans une rangée données d'une police de caractères TrueType courant.
GetCharABCWidthsW | gdi32.dll | Cette fonction permet de demander la largeur, dans son unité de mesure logique, de caractères consécutifs dans une rangée données d'une police de caractères TrueType courant avec des paramètres de chaines de caractères étendues (Unicode).
GetCharABCWidthsFloatA | gdi32.dll | Cette fonction permet de demander la largeur, dans son unité de mesure logique, de caractères consécutifs dans une rangée données d'une police de caractères courant.
GetCharABCWidthsFloatW | gdi32.dll | Cette fonction permet de demander la largeur, dans son unité de mesure logique, de caractères consécutifs dans une rangée données d'une police de caractères courant avec des paramètres de chaine de caractères étendues (Unicode).
GetCharWidth32A | gdi32.dll | Cette fonction permet demander la largeur, dans des coordonnées logiques, de caractères consécutifs dans une rangée spécifié d'une police de caractères courante.
GetCharWidth32W | gdi32.dll | Cette fonction permet demander la largeur, dans des coordonnées logiques, de caractères consécutifs dans une rangée spécifié d'une police de caractères courante avec des paramètres de chaine de caractères étendues (Unicode).
GetCharWidthA | gdi32.dll | Cette fonction permet demander la largeur, dans des coordonnées logiques, de caractères consécutifs dans une rangée spécifié d'une police de caractères courante dans un contexte de compatibilité avec les fonctions 16 bits.
GetCharWidthW | gdi32.dll | Cette fonction permet demander la largeur, dans des coordonnées logiques, de caractères consécutifs dans une rangée spécifié d'une police de caractères courante dans un contexte de compatibilité avec les fonctions 16 bits avec des paramètres de chaine de caractères étendues (Unicode).
GetCharWidthFloatA | gdi32.dll | Cette fonction permet de demander la largeur fractionné de caractères consécutifs dans une rangée spécifié de la police de caractères courante.
GetCharWidthFloatW | gdi32.dll | Cette fonction permet de demander la largeur fractionné de caractères consécutifs dans une rangée spécifié de la police de caractères courante avec des paramètres de chaine de caractères étendues (Unicode).
GetCharacterPlacementA | gdi32.dll | Cette fonction permet de demander les informations à propos d'une chaine de caractères, comme la largeur des caractères, le positionnement des carets, l'ordre dans la chaine de caractères et la rendue du glyphe.
GetCharacterPlacementW | gdi32.dll | Cette fonction permet de demander les informations à propos d'une chaine de caractères, comme la largeur des caractères, le positionnement des carets, l'ordre dans la chaine de caractères et la rendue du glyphe avec des paramètres de chaine de caractères étendues (Unicode).
GetClassInfoA | user32.dll | Cette fonction permet de demander les informations à propos d'une classe de fenêtre.
GetClassInfoExA | user32.dll | Cette fonction permet de demander les informations à propos d'une classe de fenêtre en incluant le Handle du petit icône associé avec la fenêtre de classe.
GetClassInfoExW | user32.dll | Cette fonction permet de demander les informations à propos d'une classe de fenêtre en incluant le Handle du petit icône associé avec la fenêtre de classe avec des paramètres de chaine de caractères étendues (Unicode).
GetClassInfoW | user32.dll | Cette fonction permet de demander les informations à propos d'une classe de fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
GetClassLongA | user32.dll | Cette fonction permet de demander des valeurs d'informations en 32 bits sur une classe.
GetClassLongW | user32.dll | Cette fonction permet de demander des valeurs d'informations en 32 bits sur une classe avec des paramètres de chaine de caractères étendues (Unicode).
GetClassNameA | user32.dll | Cette fonction permet de demander le nom d'une classe.
GetClassNameW | user32.dll | Cette fonction permet de demander le nom d'une classe avec des paramètres de chaine de caractères étendues (Unicode).
GetClassWord | user32.dll | Cette fonction permet de demander une valeur 16 bits de déplacement spécifié dans une classe de mémoire supplémentaire pour la classe de fenêtre à laquelle la fenêtre appartient.
GetClientRect | user32.dll | Cette fonction permet de demander les coordonnées client de la région de fenêtre client.
GetClipboardData | user32.dll | Cette fonction permet de demander le Handle du presse-papier de données courant avec le format spécifié.
GetClipboardFormatNameA | user32.dll | Cette fonction permet de demander le nom de format de presse-papier enregistré.
GetClipboardFormatNameW | user32.dll | Cette fonction permet de demander le nom de format de presse-papier enregistré avec des paramètres de chaine de caractères étendues (Unicode).
GetClipboardOwner | user32.dll | Cette fonction permet de demander le Handle de la fenêtre associé à un presse-papier.
GetClipboardViewer | user32.dll | Cette fonction permet de demander le Handle de la première fenêtre d'une chaine visualisation de presse-papier.
GetClipBox | gdi32.dll | Cette fonction permet de demander les dimensions d'un petit rectangle complètement contenu dans la région de clip courant.
GetClipCursor | user32.dll | Cette fonction permet de demander les coordonnées de la fenêtre limitant le déplacement du pointeur de la souris.
GetClipRgn | gdi32.dll | Cette fonction permet de demander le Handle d'une région de clip affecté par un périphérique de contexte spécifié.
GetColorAdjustment | gdi32.dll | Cette fonction permet de demander les valeurs de couleurs d'ajustement pour le contexte de périphérique spécifié.
GetColorSpace | gdi32.dll | Cette fonction permet de demander le Handle courant de l'espace de couleur logique du périphérique de contexte spécifié.
GetCommandLineA | kernel32.dll | Cette fonction permet de retourner la ligne de commande de l'application courante.
GetCommandLineW | kernel32.dll | Cette fonction permet de retourner la ligne de commande de l'application courante avec des paramètres de chaine de caractères étendue (Unicode).
GetCommConfig | kernel32.dll | Cette fonction permet de demander la configuration courante pour un périphérique de communication.
GetCommMask | kernel32.dll | Cette fonction permet de demander la valeur d'un masque d'événement pour un périphérique de communication spécifié.
GetCommModemStatus | kernel32.dll | Cette fonction permet de demander les valeurs du registre de contrôle du modem.
GetCommProperties | kernel32.dll | Cette fonction permet de remplir le tampon avec des informations concernant les propriétés de communications pour un périphérique de communication spécifié.
GetCommState | kernel32.dll | Cette fonction permet de remplir un bloc de contrôle de périphérique (une structure DCB) avec les ajustements de contrôle courant pour un périphérique de communication spécifié.
GetCommTimeouts | kernel32.dll | Cette fonction permet de demander les paramètres de dépassement de délai pour toutes les opérations de lecture ou d'écriture pour un périphérique de communication spécifié.
GetCompressedFileSizeA | kernel32.dll | Cette fonction permet de demander le nombre d'octets d'une unité d'entreposage actuellement utilisé pour un fichier spécifié.
GetCompressedFileSizeW | kernel32.dll | Cette fonction permet de demander le nombre d'octets d'une unité d'entreposage actuellement utilisé pour un fichier spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetComputerNameA | kernel32.dll | Cette fonction permet de demander le nom de l'ordinateur sous forme d'une chaine de caractères ASCIZ.
GetComputerNameW | kernel32.dll | Cette fonction permet de demander le nom de l'ordinateur sous forme d'une chaine de caractères ASCIZ avec des paramètres de chaine de caractères étendue (Unicode).
GetComputerNameExA | kernel32.dll | Cette fonction permet de retourner la nom NetBIOS ou DNS de l'ordinateur local.
GetComputerObjectNameA | secur32.dll | Cette fonction permet de retourner le nom de l'ordinateur dans un format spécifié.
GetConsoleCursorInfo | kernel32.dll | Cette fonction permet de demander des informations à propos de la taille et de la visibilité du curseur pour le tampon de console écran spécifié.
GetConsoleCP | kernel32.dll | Cette fonction permet de demander l'identité de code de page utilisé pour la console associé avec le processus appelant.
GetConsoleMode | kernel32.dll | Cette fonction permet de demander le mode d'entrée courant pour le tampon d'entrée de la console ou le mode de sortie courant pour le tampon d'écran de la console.
GetConsoleOutputCP | kernel32.dll | Cette fonction permet de retourner l'identité de code de page de sortie utilisé par la console associé avec le processus appelant.
GetConsoleScreenBufferInfo | kernel32.dll | Cette fonction permet de demander les informations à propos du tampon d'écran de la console spécifié.
GetConsoleTitleA | kernel32.dll | Cette fonction permet de retourner le nom de la console courante.
GetConsoleTitleW | kernel32.dll | Cette fonction permet de retourner le nom de la console courante avec des paramètres de chaine de caractères étendue (Unicode).
GetCPInfo | kernel32.dll | Cette fonction permet de retourner des informations sur le code de page spécifié.
GetCurrencyFormatA | kernel32.dll | Cette fonction permet de demander la chaine de caractères du format d'un nombre avec une chaine de caractères de monnaie pour une localité spécifié.
GetCurrencyFormatW | kernel32.dll | Cette fonction permet de demander la chaine de caractères du format d'un nombre avec une chaine de caractères de monnaie pour une localité spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetCurrentDirectoryA | kernel32.dll | Cette fonction permet de demander le répertoire courant du processus courant.
GetCurrentDirectoryW | kernel32.dll | Cette fonction permet de demander le répertoire courant du processus courant avec des paramètres de chaine de caractères étendue (Unicode).
GetCurrentHwProfileA | advapi32.dll | Cette fonction pemret de demander le profile matériel courant.
GetCurrentHwProfileW | advapi32.dll | Cette fonction pemret de demander le profile matériel courant avec des paramètres de chaine de caractères étendue (Unicode).
GetCurrentObject | gdi32.dll | Cette fonction permet de retourner l'objet sélectionné courant du type spécifié.
GetCurrentPositionEx | gdi32.dll | Cette fonction permet de demander la position courante dans des coordonnées logiques.
GetCurrentProcess | kernel32.dll | Cette fonction permet de retourner le Handle du processus courant.
GetCurrentProcessId | kernel32.dll | Cette fonction permet de demander l'identificateur de processus courant
GetCurrentProcessorNumber | kernel32.dll | Cette fonction permet de demander le nombre de processeur du processus courant lors de l'appel d'une fonction
GetCurrentThread | kernel32.dll | Cette fonction permet de retourner le Handle du processus courant.
GetCurrentThreadId | kernel32.dll | Cette fonction permet de retourner l'identificateur du processus courant.
GetCursor | user32.dll | Cette fonction permet de demander le Handle du curseur courant.
GetCursorPos | user32.dll | Cette fonction permet de demander la position du pointeur de souris à l'écran
GetDateFormatA | kernel32.dll | Cette fonction permet de demander le date dans une chaine de caractères ASCIZ selon le format spécifié.
GetDateFormatW | kernel32.dll | Cette fonction permet de demander la date dans une chaine de caractères ASCIZ selon le format spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetDC | user32.dll | Cette fonction permet de demander le contexte d'objet d'une fenêtre d'application.
GetDCEx | user32.dll | Cette fonction permet de demander le Handle du périphérique de contexte pour la fenêtre spécifié.
GetDCOrgEx | gdi32.dll | Cette fonction permet de demander l'origine de translation final d'un périphérique de contexte spécifié.
GetDefaultCommConfigA | kernel32.dll | Cette fonction permet de demander la configuration par défaut d'un périphérique de communication.
GetDefaultCommConfigW | kernel32.dll | Cette fonction permet de demander la configuration par défaut d'un périphérique de communication avec des paramètres de chaine de caractères étendue (Unicode).
GetDesktopWindow | user32.dll | Cette fonction permet de demander le Handle attribué à la fenêtre du bureau.
GetDeviceCaps | gdi32.dll | Cette fonction permet de demander les informations d'un périphérique spécifié à propos du périphérique d'affichage.
GetDeviceGammaRamp | gdi32.dll | Cette fonction permet de demander la rampe gamma d'un panneau d'affichage de couleur directe.
GetDialogBaseUnits | user32.dll | Cette fonction permet de retourner l'unité de base de la boite de dialogue qu'utilise Windows quand il créer une boite de dialogue.
GetDIBColorTable | gdi32.dll | Cette fonction permet de demander les valeurs de couleurs RVB (rouge, vert, bleu) d'une rangée d'entrées d'une table de couleurs d'une section DIB Bitmap étant actuellement sélectionné dans un périphérique de contexte spécifié.
GetDIBits | gdi32.dll | Cette fonction permet de demander les bits d'un Bitmap compatible et copie celui-ci dans un tampon DIB en utilisant le format spécifié.
GetDiskFreeSpaceA | kernel32.dll | Cette fonction permet de demander l'espace de libre sur une unité disque.
GetDiskFreeSpaceExA | kernel32.dll | Cette fonction permet de demander des informations global sur l'espace de libre sur une unité disque.
GetDiskFreeSpaceExW | kernel32.dll | Cette fonction permet de demander des informations global sur l'espace de libre sur une unité disque avec des paramètres de chaine de caractères étendue (Unicode).
GetDiskFreeSpaceW | kernel32.dll | Cette fonction permet de demander l'espace de libre sur une unité disque avec des paramètres de chaine de caractères étendue (Unicode).
GetDlgCtrlID | user32.dll | Cette fonction permet de retourner le Handle de la fenêtre enfant.
GetDlgItem | user32.dll | Cette fonction permet de demander le Handle d'un contrôle étant données par la boite de dialogue.
GetDlgItemInt | user32.dll | Cette fonction permet d'effectuer la translation du texte d'un contrôle d'une boite de dialogue en une valeur entière.
GetDlgItemTextA | user32.dll | Cette fonction permet de demander le titre ou le texte associé avec un contrôle de boite de dialogue.
GetDlgItemTextW | user32.dll | Cette fonction permet de demander le titre ou le texte associé avec un contrôle de boite de dialogue avec des paramètres de chaine de caractères étendues (Unicode).
GetDoubleClickTime | user32.dll | Cette fonction permet de demander le temps maximal autorisé pour que des clics sur les boutons de la souris soit considéré comme un double-clic.
GetDriveTypeA | kernel32.dll | Cette fonction permet de demander le type d'unité disque.
GetDriveTypeW | kernel32.dll | Cette fonction permet de demander le type d'unité disque avec des paramètres de chaine de caractères étendue (Unicode).
GetEnhMetaFileA | gdi32.dll | Cette fonction permet de créer un Handle étant identifié par un fichier méta de format étendue entreposé dans le fichier spécifié.
GetEnhMetaFileW | gdi32.dll | Cette fonction permet de créer un Handle étant identifié par un fichier méta de format étendue entreposé dans le fichier spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetEnhMetaFileBits | gdi32.dll | Cette fonction permet de demander le contenu d'un fichier méta spécifié de format étendue et le copie dans un tampon.
GetEnhMetaFileDescriptionA | gdi32.dll | Cette fonction permet de demander une description texte optionnel d'un fichier méta de format étendue et copie la chaine de caractères dans un tampon spécifié.
GetEnhMetaFileDescriptionW | gdi32.dll | Cette fonction permet de demander une description texte optionnel d'un fichier méta de format étendue et copie la chaine de caractères dans un tampon spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetEnhMetaFileHeader | gdi32.dll | Cette fonction permet de demander l'enregistrement contenu dans l'entête du fichier méta spécifié de format étendue.
GetEnhMetaFilePaletteEntries | gdi32.dll | Cette fonction permet de demander les entrées de palette optionnel pour le fichier méta étendue spécifié.
GetEnhMetaFilePixelFormat | gdi32.dll | Cette fonction permet de demander les informations de format de pixel pour un fichier méta étendue.
GetEnvironmentStringsA | kernel32.dll | Cette fonction permet de demander les variables d'environnements du processus courant.
GetEnvironmentStringsW | kernel32.dll | Cette fonction permet de demander les variables d'environnements du processus courant avec des paramètres de chaine de caractères étendue (Unicode).
GetEnvironmentVariableA | kernel32.dll | Cette fonction permet de demander la valeur d'une variable d'environnement du système d'exploitation.
GetEnvironmentVariableW | kernel32.dll | Cette fonction permet de demander la valeur d'une variable d'environnement du système d'exploitation avec des paramètres de chaine de caractères étendue (Unicode).
GetExitCodeProcess | kernel32.dll | Cette fonction permet de demander le code d'état de terminaison d'un processus spécifié.
GetExitCodeThread | kernel32.dll | Cette fonction permet de demander le code d'état de terminaison d'un processus léger (Thread) spécifié.
GetFileAttributesA | kernel32.dll | Cette fonction permet de demander les attributs d'un fichier ou d'un répertoire.
GetFileAttributesExA | kernel32.dll | Cette fonction permet de demander les informations étendues d'attributs d'un fichier ou d'un répertoire.
GetFileAttributesExW | kernel32.dll | Cette fonction permet de demander les informations étendues d'attributs d'un fichier ou d'un répertoire avec des paramètres de chaine de caractères étendue (Unicode).
GetFileAttributesW | kernel32.dll | Cette fonction permet de demander les attributs d'un fichier ou d'un répertoire avec des paramètres de chaine de caractères étendue (Unicode).
GetFileInformationByHandle | kernel32.dll | Cette fonction permet de demander les informations associé au fichier spécifié.
GetFileSecurityA | advapi32.dll | Cette fonction permet de demander les informations spécifié sur la sécurité d'un fichier ou d'un répertoire.
GetFileSecurityW | advapi32.dll | Cette fonction permet de demander les informations spécifié sur la sécurité d'un fichier ou d'un répertoire avec des paramètres de chaine de caractères étendue (Unicode).
GetFileSize | kernel32.dll | Cette fonction permet de demander la taille, en octets, d'un fichier spécifié.
GetFileTime | kernel32.dll | Cette fonction permet de demander la date et l'heure d'un fichier de sa création, de sa dernière modification ou de son dernière accès.
GetFileType | kernel32.dll | Cette fonction permet de retourner le type d'un fichier spécifié.
GetFileVersionInfoSizeA | version.dll | Cette fonction permet de demander la taille de l'entête d'information de version du fichier spécifié.
GetFocus | user32.dll | Cette fonction permet de demander le focus d'entrée de la fenêtre spécifié.
GetFontData | gdi32.dll | Cette fonction permet de demander les données de police de caractères métriques pour une police de caractères TrueType.
GetFontLanguageInfo | gdi32.dll | Cette fonction permet de retourner les informations à propos de sélection de police de caractères courante dans l'affichage de contexte spécifié.
GetForegroundWindow | user32.dll | Cette fonction permet de demander le Handle de la fenêtre d'application courante.
GetFullPathNameA | kernel32.dll | Cette fonction permet de demander le chemin et le nom de fichier complet pour un fichier spécifié.
GetFullPathNameW | kernel32.dll | Cette fonction permet de demander le chemin et le nom de fichier complet pour un fichier spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetGlyphOutlineA | gdi32.dll | Cette fonction permet de demander ligne de limite ou du Bitmap d'un caractère d'une police de caractères TrueType étant sélectionné dans le périphérique de contexte spécifié.
GetGlyphOutlineW | gdi32.dll | Cette fonction permet de demander ligne de limite ou du Bitmap d'un caractère d'une police de caractères TrueType étant sélectionné dans le périphérique de contexte spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetGraphicsMode | gdi32.dll | Cette fonction permet de demander le mode graphique courant pour le périphérique de contexte courant.
GetGuiResources | kernel32.dll | Cette fonction permet de demander le nombre d'identificateur de GUI (Interface graphique utilisateur) du processus courant.
GetHandleInformation | kernel32.dll | Cette fonction permet de demander les informations conçernant certaines propriétés d'un objet avec un identificateur Handle.
GetICMProfileA | gdi32.dll | Cette fonction permet de demander le nom du fichier de profile de couleurs pour le périphérique associé avec le périphérique de contexte spécifié.
GetICMProfileW | gdi32.dll | Cette fonction permet de demander le nom du fichier de profile de couleurs pour le périphérique associé avec le périphérique de contexte spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetIconInfo | user32.dll | Cette fonction permet de demander les informations à propos de l'icône ou du curseur spécifié.
GetInputState | user32.dll | Cette fonction permet d'indiquer si un événement de clic de souris ou de clavier est survenue dans la file d'attente du système du traitement requis.
GetIpStatistics | iphlpapi.dll | Cette fonction permet de demander les informations de statistique sur le IP de l'ordinateur.
GetJobA | winspool.drv | Cette fonction permet de demander les données d'un travail contenu dans le tampon d'impressions de l'imprimante.
GetKBCodePage | user32.dll | Cette fonction retourne le code de page chargé par le système d'exploitation Windows.
GetKernelObjectSecurity | advapi32.dll | Cette fonction permet de demander une copie du descripteur de sécurité protégé d'un objet du noyau du système d'exploitation.
GetKerningPairs | gdi32.dll | Cette fonction permet de demander les paires de crénages de caractères pour la police de caractères courante sélectionné du périphérique de contexte spécifié.
GetKeyboardLayout | user32.dll | Cette fonction permet de demander la couche de clavier active pour le processus léger spécifié.
GetKeyboardLayoutList | user32.dll | Cette fonction permet de demander le Handle de la couche de clavier correspondant à l'ensemble d'entrée courant local dans le système.
GetKeyboardLayoutNameA | user32.dll | Cette fonction permet de demander le nom des couches de clavier active.
GetKeyboardLayoutNameW | user32.dll | Cette fonction permet de demander le nom des couches de clavier active avec des paramètres de chaine de caractères étendues (Unicode).
GetKeyboardState | user32.dll | Cette fonction permet de demander l'état des 256 touches virtuel de clavier.
GetKeyboardType | user32.dll | Cette fonction permet de demander les informations à propos du clavier courant.
GetKeyNameTextA | user32.dll | Cette fonction permet de demander une chaine de caractères représentant le nom d'une touche du clavier.
GetKeyNameTextW | user32.dll | Cette fonction permet de demander une chaine de caractères représentant le nom d'une touche du clavier avec des paramètres de chaine de caractères étendues (Unicode).
GetKeyState | user32.dll | Cette fonction permet de demander l'état d'une touche virtuel de clavier.
GetLargestConsoleWindowSize | kernel32.dll | Cette fonction permet de demander la taille de la fenêtre de console la plus large possible en se basant sur la police de caractères et la taille d'affichage.
GetLastActivePopup | user32.dll | Cette fonction permet de demander laquelle des fenêtres du propriétaire de la fenêtre POP-UP a été récemment activé.
GetLastError | kernel32.dll | Cette fonction permet de demander la dernière erreur s'étant produite dans une API.
GetLengthSid | advapi32.dll | Cette fonction permet de retourner la longueur, en octets, d'une structure valide d'identificateur de sécurité (SID).
GetLocaleInfoA | kernel32.dll | Cette fonction permet de demander des informations de nature local sur le système.
GetLocaleInfoW | kernel32.dll | Cette fonction permet de demander des informations de nature local sur le système avec des paramètres de chaine de caractères étendue (Unicode).
GetLocalTime | kernel32.dll | Cette fonction permet de demander la date et l'heure local du système.
GetLogColorSpaceA | gdi32.dll | Cette fonction permet de demander les informations à propos de l'espace de couleurs logiques identifié par le Handle spécifié.
GetLogColorSpaceW | gdi32.dll | Cette fonction permet de demander les informations à propos de l'espace de couleurs logiques identifié par le Handle spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetLogicalDrives | kernel32.dll | Cette fonction permet de demander quel sont les unités de disque valide.
GetLogicalDriveStringsA | kernel32.dll | Cette fonction permet de remplir un tampon avec une chaine de caractères spécifiant une unité de disque système valide.
GetLogicalDriveStringsW | kernel32.dll | Cette fonction permet de remplir un tampon avec une chaine de caractères spécifiant une unité de disque système valide avec des paramètres de chaine de caractères étendue (Unicode).
GetLogicalProcessorInformation | kernel32.dll | Cette fonction permet de demander des informations concernant les processeurs logiques et le matériel associé.
GetMailslotInfo | kernel32.dll | Cette fonction permet de demander des informations pour une slot de courriel.
GetMapMode | gdi32.dll | Cette fonction permet de demander le mode de cartographie courant.
GetMenu | user32.dll | Cette fonction permet de demander le Handle du menu déroulant de la fenêtre d'application spécifié.
GetMenuCheckMarkDimensions | user32.dll | Cette fonction permet de retourner les dimensions du Bitmap marqué par défaut.
GetMenuContextHelpId | user32.dll | Cette fonction permet de demander l'identifiant de contexte d'aide associé avec le menu spécifié.
GetMenuDefaultItem | user32.dll | Cette fonction permet de demander l'item de menu par défaut du menu spécifié.
GetMenuItemCount | user32.dll | Cette fonction permet de demander le nombre d'item que contient menu déroulant.
GetMenuItemID | user32.dll | Cette fonction permet de demander les identificateurs d'un emplacement d'item de menu d'une position spécifié.
GetMenuItemInfoA | user32.dll | Cette fonction permet de demander les informations à propos de l'item de menu.
GetMenuItemInfoW | user32.dll | Cette fonction permet de demander les informations à propos de l'item de menu avec des paramètres de chaine de caractères étendues (Unicode).
GetMenuItemRect | user32.dll | Cette fonction permet de demander les limites rectangulaires pour l'item de menu spécifié.
GetMenuState | user32.dll | Cette fonction permet de demander l'état des drapeaux associés avec l'item de menu spécifié.
GetMenuStringA | user32.dll | Cette fonction permet de copier l'étiquette d'un item de menu dans un tampon.
GetMenuStringW | user32.dll | Cette fonction permet de copier l'étiquette d'un item de menu dans un tampon avec des paramètres de chaine de caractères étendues (Unicode).
GetMessageA | user32.dll | Cette fonction permet de demander un message d'une application de file d'attente de message dans une structure spécifié.
GetMessageW | user32.dll | Cette fonction permet de demander un message d'une application de file d'attente de message dans une structure spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetMessageExtraInfo | user32.dll | Cette fonction permet de demander les informations supplémentaires associés avec le dernier message des fonctions GetMessage ou PeekMessage.
GetMessagePos | user32.dll | Cette fonction permet de retourner une valeur long représentant une position de curseur dans une coordonnée d'écran.
GetMessageTime | user32.dll | Cette fonction permet de retourner l'heure du dernier message reçu par la fonction GetMessage.
GetMetaFileA | gdi32.dll | Cette fonction permet de créer un Handle identifiant le format Windows de fichier de méta données.
GetMetaFileW | gdi32.dll | Cette fonction permet de créer un Handle identifiant le format Windows de fichier de méta données avec des paramètres de chaine de caractères étendues (Unicode).
GetMetaFileBitsEx | gdi32.dll | Cette fonction permet de demander le contenu d'un fichier méta de format Windows et copie le contenu dans un tampon spécifié.
GetMetaRgn | gdi32.dll | Cette fonction permet de demander la région méta courante pour le périphérique de contexte spécifié.
GetMiterLimit | gdi32.dll | Cette fonction permet de demander la limite mitre pour le périphérique de contexte spécifié.
GetModuleFileNameA | kernel32.dll | Cette fonction permet de demander un nom de fichier long ou demande le nom complet de chemin et nom de fichier pour un fichier exécutable contenu dans le module spécifié.
GetModuleFileNameW | kernel32.dll | Cette fonction permet de demander un nom de fichier long ou demande le nom complet de chemin et nom de fichier pour un fichier exécutable contenu dans le module spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetModuleHandleA | kernel32.dll | Cette fonction permet de retourner l'identificateur Handle du module spécifié si le fichier est situé dans l'espace d'adresse d'un processus appelant.
GetModuleHandleW | kernel32.dll | Cette fonction permet de retourner l'identificateur Handle du module spécifié si le fichier est situé dans l'espace d'adresse d'un processus appelant avec des paramètres de chaine de caractères étendue (Unicode).
GetNamedPipeHandleStateA | kernel32.dll | Cette fonction permet de demander les informations d'état conçernant le nom du tuyau spécifié.
GetNamedPipeHandleStateW | kernel32.dll | Cette fonction permet de demander les informations d'état conçernant le nom du tuyau spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetNamedPipeInfo | kernel32.dll | Cette fonction permet de demander les informations conçernant le nom de tuyau spécifié.
GetNearestColor | gdi32.dll | Cette fonction permet de demander la couleur solide correspondant le mieux à la couleur logique.
GetNearestPaletteIndex | gdi32.dll | Cette fonction permet de demander l'index d'une entrée de palette logique correspondant le mieux à la valeur de couleur spécifié.
GetNextDlgGroupItem | user32.dll | Cette fonction permet de rechercher le contrôle précédent ou suivant d'un groupe de contrôle dans une boite de dialogue.
GetNextDlgTabItem | user32.dll | Cette fonction permet de demander le Handle du premier contrôle d'un style WS_TABSTOP du contrôle spécifié.
GetNextWindow | user32.dll | Cette fonction permet de rechercher le Handle de la prochaine ou précédente fenêtre dans la liste du gestionnaire de fenêtre.
GetNumaAvailableMemoryNode | kernel32.dll | Cette fonction permet de demander la quantité de mémoire disponible dans le noeud spécifié.
GetNumberFormatA | kernel32.dll | Cette fonction permet de transformer un nombre selon le format d'affichage spécifié.
GetNumberFormatW | kernel32.dll | Cette fonction permet de transformer un nombre selon le format d'affichage spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetNumberOfConsoleInputEvents | kernel32.dll | Cette fonction permet de demander le nombre d'enregistrement d'entrée non lue dans le tampon d'entrée de console.
GetNumberOfConsoleMouseButtons | kernel32.dll | Cette fonction permet de demander le nombre de boutons de la souris qu'utilise la console courante.
GetNumberOfEventLogRecords | advapi32.dll | Cette fonction permet de demander le nombre d'enregistrement de l'événement du journal de bord spécifié.
GetObjectA | gdi32.dll | Cette fonction permet de remplir un tampon avec les informations définit dans la région spécifié.
GetObjectW | gdi32.dll | Cette fonction permet de remplir un tampon avec les informations définit dans la région spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetObjectType | gdi32.dll | Cette fonction permet de demander l'identifiant de type d'objet spécifié.
GetOEMCP | kernel32.dll | Cette fonction permet de demander le code de page OEM du système d'exploitation.
GetOldestEventLogRecord | advapi32.dll | Cette fonction permet de demander le nombre d'enregistrement dans les anciens enregistrement du journal de bord spécifié.
GetOpenClipboardWindow | user32.dll | Cette fonction permet de demander le Handle de la fenêtre actuellement ouverte dans le presse-papier.
GetOpenFileNameA | comdlg32.dll | Cette fonction permet d'afficher une boite de dialogue offrant la possibilité de choisir un fichier.
GetOutlineTextMetricsA | gdi32.dll | Cette fonction permet de demander le métrique du texte pour la police de caractères TrueType.
GetOutlineTextMetricsW | gdi32.dll | Cette fonction permet de demander le métrique du texte pour la police de caractères TrueType avec des paramètres de chaine de caractères étendues (Unicode).
GetOverlappedResult | kernel32.dll | Cette fonction permet de retourner le résultat d'une opération de recouvrement sur un fichier, un tuyau ou un périphérique de communications spécifié
GetPaletteEntries | gdi32.dll | Cette fonction permet de demander le rang des entrées d'une palette dans une palette logique.
GetParent | user32.dll | Cette fonction permet de demander le Handle de la fenêtre parent spécifié.
GetPath | gdi32.dll | Cette fonction permet de demander les coordonnées définit par des points de fin de lignes et une courbe de point de contrôle trouvé dans un chemin étant sélectionné dans un périphérique de contexte spécifié.
GetPixel | gdi32.dll | Cette fonction permet de demander la couleur sous format RVB (RGB) d'un pixel affiché à l'écran.
GetPixelFormat | gdi32.dll | Cette fonction permet de demander l'index du format de pixel actuellement sélectionné dans le périphérique de contexte spécifié.
GetPolyFillMode | gdi32.dll | Cette fonction permet de demander le mode de remplissage d'un polygone.
GetPrinterA | winspool.drv | Cette fonction permet de demander des informations sur une imprimante.
GetPriorityClass | kernel32.dll | Cette fonction permet de retourner la priorité d'une classe pour un processus spécifié.
GetPriorityClipboardFormat | user32.dll | Cette fonction permet de demander le format du premier presse-papier dans la liste de données existantes du presse-papier.
GetPrivateObjectSecurity | advapi32.dll | Cette fonction permet de demander des informations de descripteur de sécurité à un serveur d'objet protégé.
GetPrivateProfileIntA | kernel32.dll | Cette fonction permet de demander un entier associé avec une clef dans une section spécifié d'un fichier initialisé.
GetPrivateProfileIntW | kernel32.dll | Cette fonction permet de demander un entier associé avec une clef dans une section spécifié d'un fichier initialisé avec des paramètres de chaine de caractères étendue (Unicode).
GetPrivateProfileSectionA | kernel32.dll | Cette fonction permet de demander tous les valeurs et clefs pour la section initialisé spécifié.
GetPrivateProfileSectionW | kernel32.dll | Cette fonction permet de demander tous les valeurs et clefs pour la section initialisé spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetPrivateProfileSectionNamesA | kernel32.dll | Cette fonction permet de demander les noms de toutes les sections dans un fichier initialisé.
GetPrivateProfileSectionNamesW | kernel32.dll | Cette fonction permet de demander les noms de toutes les sections dans un fichier initialisé avec des paramètres de chaine de caractères étendue (Unicode).
GetPrivateProfileStringA | kernel32.dll | Cette fonction permet de demander une chaine de caractères de la section spécifié dans un fichier initialisé.
GetPrivateProfileStringW | kernel32.dll | Cette fonction permet de demander une chaine de caractères de la section spécifié dans un fichier initialisé avec des paramètres de chaine de caractères étendue (Unicode).
GetProcAddress | kernel32.dll | Cette fonction permet de demander l'adresse d'une fonction d'exportation de bibliothèque de liens dynamique (DLL).
GetProcessAffinityMask | kernel32.dll | Cette fonction permet de demander un masque de correspondance de processus spécifié avec le système.
GetProcessHeap | kernel32.dll | Cette fonction permet de demander un identificateur Handle pour la mémoire de tas d'un processus appelant.
GetProcessHeaps | kernel32.dll | Cette fonction permet de demander un identificateur Handle pour tous les mémoires de tas valides pour un processus appelant.
GetProcessPriorityBoost | kernel32.dll | Cette fonction permet de demander l'état de la priorité de contrôle d'amplificateur d'un processus spécifié.
GetProcessShutdownParameters | kernel32.dll | Cette fonction permet de demander les paramètres de fermetures et d'éteindre le système d'exploitation pour un le processus appelant courant.
GetProcessTimes | kernel32.dll | Cette fonction permet de demander les informations d'horloge pour le processus spécifié.
GetProcessVersion | kernel32.dll | Cette fonction permet de demander la version majeur et mineur pour un processus spécifié en exécution.
GetProcessWindowStation | user32.dll | Cette fonction permet de demander le Handle de la fenêtre de station associé avec le processus appelant.
GetProcessWorkingSetSize | kernel32.dll | Cette fonction permet de demander la taille de l'espace de travail minimum et maximum pour le processus spécifié.
GetProfileIntA | kernel32.dll | Cette fonction permet de demander un entier pour le nom de clef spécifié dans une section spécifié dans un fichier «WIN.INI».
GetProfileIntW | kernel32.dll | Cette fonction permet de demander un entier pour le nom de clef spécifié dans une section spécifié dans un fichier «WIN.INI» avec des paramètres de chaine de caractères étendue (Unicode).
GetProfileSectionA | kernel32.dll | Cette fonction permet de demander tous les valeurs et clefs pour la section spécifié dans un fichier «WIN.INI».
GetProfileSectionW | kernel32.dll | Cette fonction permet de demander tous les valeurs et clefs pour la section spécifié dans un fichier «WIN.INI» avec des paramètres de chaine de caractères étendue (Unicode).
GetProfileStringA | kernel32.dll | Cette fonction permet de demander une chaine de caractères associé avec la clef spécifié dans une section du fichier «WIN.INI».
GetProfileStringW | kernel32.dll | Cette fonction permet de demander une chaine de caractères associé avec la clef spécifié dans une section du fichier «WIN.INI» avec des paramètres de chaine de caractères étendue (Unicode).
GetPropA | user32.dll | Cette fonction permet de demander une données Handle d'une liste de propriété d'une fenêtre.
GetPropW | user32.dll | Cette fonction permet de demander une données Handle d'une liste de propriété d'une fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
GetQueuedCompletionStatus | kernel32.dll | Cette fonction permet d'essayer de compléter la file d'attente d'un paquet d'entrée/sortie spécifié pour un port d'entrée/sortie.
GetQueueStatus | user32.dll | Cette fonction permet de retourner une valeur indiquant le type de message dans la file d'attente.
GetRasterizerCaps | gdi32.dll | Cette fonction permet de retourner les drapeaux indiquant si la police de caractères TrueType est installé dans le système.
GetRegionData | gdi32.dll | Cette fonction permet de remplir le tampons spécifié avec une région de données décrites.
GetRgnBox | gdi32.dll | Cette fonction permet de demander les coordonnées des limites rectangulaires d'une région spécifié.
GetROP2 | gdi32.dll | Cette fonction permet de demander le mode dessinage courant.
GetScrollInfo | user32.dll | Cette fonction permet de demander les paramètres d'une barre de défilement, en incluant les positions de défilement minimum et maximum, la taille de la page et la position de la boite de défilement.
GetScrollPos | user32.dll | Cette fonction permet de demander la position courante de la boite de défilement d'une barre de défilement.
GetScrollRange | user32.dll | Cette fonction permet de demander la position minimum et maximum de la barre de défilement spécifié.
GetSecurityDescriptorControl | advapi32.dll | Cette fonction permet de demander le contrôle de descripteur de sécurité et les informations de révision.
GetSecurityDescriptorDacl | advapi32.dll | Cette fonction permet de demander un pointeur sur une liste de contrôle d'accès (ACL) discrétionnaire dans un descripteur de sécurité spécifié.
GetSecurityDescriptorGroup | advapi32.dll | Cette fonction permet de demander les informations de groupe primaire d'un descripteur de sécurité.
GetSecurityDescriptorLength | advapi32.dll | Cette fonction permet de retourner la longueur, en octets, d'une structure valide de descripteur de sécurité.
GetSecurityDescriptorOwner | advapi32.dll | Cette fonction permet de demander les informations du propriétaire d'un descripteur de sécurité
GetSecurityDescriptorSacl | advapi32.dll | Cette fonction permet de demander un pointeur sur la liste de contrôle d'accès système (ACL) dans un descripteur de sécurité spécifié.
GetShortPathNameA | kernel32.dll | Cette fonction permet de demander, à partir d'un nom long, le nom en format DOS (8 caractères de nom et 3 caractères d'extension) d'un fichier.
GetShortPathNameW | kernel32.dll | Cette fonction permet de demander, à partir d'un nom long, le nom en format DOS (8 caractères de nom et 3 caractères d'extension) d'un fichier avec des paramètres de chaine de caractères étendue (Unicode).
GetSidIdentifierAuthority | advapi32.dll | Cette fonction permet de retourner l'adresse de la structure d'un «SID_IDENTIFIER_AUTHORITY» dans l'identificateur de sécurité (SID) spécifié.
GetSidLengthRequired | advapi32.dll | Cette fonction permet de retourner la longueur, en octets, d'un tampon requis pour l'entreposage d'une structure SID avec le nombre de sous-autorisation spécifié.
GetSidSubAuthority | advapi32.dll | Cette fonction permet de retourner l'adresse d'une sous-autorité spécifié d'une structure SID.
GetSidSubAuthorityCount | advapi32.dll | Cette fonction permet de retourner l'adresse d'un champs d'une structure SID contenant le compteur de sous-autorité.
GetStartupInfoA | kernel32.dll | Cette fonction permet de demander le contenu de la structure «STARTUPINFO» spécifié quand un appel de processus est créé.
GetStartupInfoW | kernel32.dll | Cette fonction permet de demander le contenu de la structure «STARTUPINFO» spécifié quand un appel de processus est créé avec des paramètres de chaine de caractères étendue (Unicode).
GetStdHandle | kernel32.dll | Cette fonction permet de demander le Handle d'une entrée ou sortie standard.
GetStockObject | gdi32.dll | Cette fonction permet de demander le Handle d'un des outils de stylo, brosse, police de caractères,... des objets de Windows
GetStretchBltMode | gdi32.dll | Cette fonction permet de demander le mode d'étirement du Bitmap courant.
GetStringTypeA | kernel32.dll | Cette fonction permet de retourner des informations type de caractère pour la chaine de caractères spécifié.
GetStringTypeExA | kernel32.dll | Cette fonction permet de retourner des informations type de caractère pour la chaine de caractères spécifié en Win32.
GetStringTypeExW | kernel32.dll | Cette fonction permet de retourner des informations type de caractère pour la chaine de caractères spécifié en Win32 avec des paramètres de chaine de caractères étendue (Unicode).
GetStringTypeW | kernel32.dll | Cette fonction permet de retourner des informations type de caractère pour la chaine de caractères spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetSubMenu | user32.dll | Cette fonction permet de demander le Handle du menu contextuel.
GetSysColor | user32.dll | Cette fonction permet de demander la couleur d'un des objets affichés du système d'exploitations de Windows.
GetSysColorBrush | user32.dll | Cette fonction permet de demander le Handle identifiant la brosse logique correspondant à l'index de couleur spécifié.
GetSystemDefaultLCID | kernel32.dll | Cette fonction permet de demander l'identificateur locale du système par défaut.
GetSystemDefaultLangID | kernel32.dll | Cette fonction permet de demander l'identificateur de langage système par défaut.
GetSystemDirectoryA | kernel32.dll | Cette fonction permet de demander le répertoire dans lequel les fichiers systèmes du système d'exploitations de Windows sont situés.
GetSystemDirectoryW | kernel32.dll | Cette fonction permet de demander le répertoire dans lequel les fichiers systèmes du système d'exploitations de Windows sont situés avec des paramètres de chaine de caractères étendue (Unicode).
GetSystemInfo | kernel32.dll | Cette fonction permet de demander divers informations à propos du système courant et du microprocesseur.
GetSystemMenu | user32.dll | Cette fonction permet de demander le Handle du menu système d'une fenêtre d'application.
GetSystemMetrics | user32.dll | Cette fonction permet de demander le type de système métrique ou de ses paramètres utilisés.
GetSystemPaletteEntries | gdi32.dll | Cette fonction permet de demander un rang dans les entrées de palette de la palette système.
GetSystemPaletteUse | gdi32.dll | Cette fonction permet de demander si une application est accessible à une palette d'entrée système.
GetSystemPowerStatus | kernel32.dll | Cette fonction permet de demander l'état de l'alimentation en énergie de l'ordinateur.
GetSystemTime | kernel32.dll | Cette fonction permet de demander le temps (date et heure) courant du système d'exploitation. Le temps du système d'exploitation est une expression UTC (Coordinated Universal Time).
GetSystemTimeAsFileTime | kernel32.dll | Cette fonction permet de demander la date et l'heure du système d'exploitation.
GetSystemTimeAdjustment | kernel32.dll | Cette fonction permet de demander si le système applique périodiquement un ajustement du temps après chaque tic d'horloge.
GetTabbedTextExtentA | user32.dll | Cette fonction permet de calculer la largeur et la hauteur d'une chaine de caractères.
GetTabbedTextExtentW | user32.dll | Cette fonction permet de calculer la largeur et la hauteur d'une chaine de caractères avec des paramètres de chaine de caractères étendues (Unicode).
GetTapeParameters | kernel32.dll | Cette fonction permet de demander des informations descriptives sur l'unité de bande magnétique de copie de sauvegarde.
GetTapePosition | kernel32.dll | Cette fonction permet de demander l'adresse courante logique ou en bloc absolue sur une bande magnétique de copie de sauvegarde.
GetTapeStatus | kernel32.dll | Cette fonction permet de demander si la bande magnétique de copie de sauvegarde est prête à recevoir une commande de processus.
GetTcpStatistics | iphlpapi.dll | Cette fonction permet de demander les informations de statistique sur le TCP de l'ordinateur.
GetTempFileNameA | kernel32.dll | Cette fonction permet de demander un nom de fichier créer temporairement.
GetTempFileNameW | kernel32.dll | Cette fonction permet de demander un nom de fichier créer temporairement avec des paramètres de chaine de caractères étendue (Unicode).
GetTempPathA | kernel32.dll | Cette fonction permet de demander le répertoire par défaut des fichiers temporairement.
GetTempPathW | kernel32.dll | Cette fonction permet de demander le répertoire par défaut des fichiers temporairement avec des paramètres de chaine de caractères étendue (Unicode).
GetTextAlign | gdi32.dll | Cette fonction permet de demander l'état des drapeaux d'alignement de texte pour un périphérique de contexte spécifié.
GetTextCharacterExtra | gdi32.dll | Cette fonction permet de demander l'ajustement courant pour la quantité d'espacement entre les caractères.
GetTextCharset | gdi32.dll | Cette fonction permet de demander les informations à propos d'un ensemble de caractères de la police de caractères actuellement sélectionné.
GetTextCharsetInfo | gdi32.dll | Cette fonction permet de demander les informations supplémentaires à propos de l'ensemble de caractères de la police de caractères actuellement sélectionné.
GetTextColor | gdi32.dll | Cette fonction permet de demander la couleur de texte courante.
GetTextExtentExPointA | gdi32.dll | Cette fonction permet de demander le nombre de caractères dans la chaine de caractères correspondant à l'espace spécifié et remplit un tableau avec le texte étendue pour chacun de ses caractères.
GetTextExtentExPointW | gdi32.dll | Cette fonction permet de demander le nombre de caractères dans la chaine de caractères correspondant à l'espace spécifié et remplit un tableau avec le texte étendue pour chacun de ses caractères avec des paramètres de chaine de caractères étendues (Unicode).
GetTextExtentPoint32A | gdi32.dll | Cette fonction permet de calculer la largeur et la hauteur de la chaine de caractères de texte spécifié.
GetTextExtentPoint32W | gdi32.dll | Cette fonction permet de calculer la largeur et la hauteur de la chaine de caractères de texte spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetTextExtentPointA | gdi32.dll | Cette fonction permet de calculer la largeur et la hauteur d'un chaine de caractères de texte spécifié dans un contexte de compatibilité 16 bits.
GetTextExtentPointW | gdi32.dll | Cette fonction permet de calculer la largeur et la hauteur d'un chaine de caractères de texte spécifié dans un contexte de compatibilité 16 bits avec des paramètres de chaine de caractères étendues (Unicode).
GetTextFaceA | gdi32.dll | Cette fonction permet de demander le nom de type de police de caractères étant sélectionné dans le périphérique de contexte spécifié.
GetTextFaceW | gdi32.dll | Cette fonction permet de demander le nom de type de police de caractères étant sélectionné dans le périphérique de contexte spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetTextMetricsA | gdi32.dll | Cette fonction permet de demander les informations métriques d'une police de caractères courante.
GetTextMetricsW | gdi32.dll | Cette fonction permet de demander les informations métriques d'une police de caractères courante avec des paramètres de chaine de caractères étendues (Unicode).
GetThreadContext | kernel32.dll | Cette fonction permet de demander le contexte d'un processus léger spécifié.
GetThreadDesktop | user32.dll | Cette fonction permet de demander le Handle du bureau associé avec le processus spécifié.
GetThreadLocale | kernel32.dll | Cette fonction permet de demander l'identificateur local d'un processus.
GetThreadPriority | kernel32.dll | Cette fonction permet de retourner la valeur de priorité du processus léger spécifié.
GetThreadPriorityBoost | kernel32.dll | Cette fonction permet de retourner la priorité de lancement d'un processus léger.
GetThreadSelectorEntry | kernel32.dll | Cette fonction permet de retourner l'entrée de descripteur de table pour le sélecteur et le processus léger spécifié.
GetThreadTimes | kernel32.dll | Cette fonction permet de demander les informations de temps à propos du processus léger spécifié.
GetTickCount | kernel32.dll | Cette fonction permet de demander le nombre de milliseconde écoulé depuis le démarrage de Windows.
GetTimeFormatA | kernel32.dll | Cette fonction permet de retourner la chaine de caractères du format du temps localement.
GetTimeFormatW | kernel32.dll | Cette fonction permet de retourner la chaine de caractères du format du temps localement avec des paramètres de chaine de caractères étendue (Unicode).
GetTimeZoneInformation | kernel32.dll | Cette fonction permet de demander les informations de la zone horaire courante du système d'exploitation Windows.
GetTokenInformation | advapi32.dll | Cette fonction permet de demander le type d'information associé à un accès de jeton.
GetTopWindow | user32.dll | Cette fonction permet de demander le Handle du plus haut niveau de fenêtre enfant appartenant à la fenêtre parent spécifié.
GetUdpStatistics | iphlpapi.dll | Cette fonction permet de demander les informations de statistique sur le UDP de l'ordinateur.
GetUpdateRect | user32.dll | Cette fonction permet de demander les coordonnées d'un petit rectangle complètement enfermé dans une région mise à jour par la fenêtre donnée.
GetUpdateRgn | user32.dll | Cette fonction permet de demander la région mise à jour dans la fenêtre.
GetUserDefaultLCID | kernel32.dll | Cette fonction permet de demander l'identificateur local de l'utilisateur par défaut.
GetUserNameA | advapi32.dll | Cette fonction permet de demander le nom de l'utilisateur courant actuellement sous Windows.
GetUserNameW | advapi32.dll | Cette fonction permet de demander le nom de l'utilisateur courant actuellement sous le système d'exploitation Windows avec des paramètres de chaine de caractères étendue (Unicode).
GetUserNameExA | secur32.dll | Cette fonction permet de retourner le nom de l'utilisateur dans le format EXTENDED_NAME_FORMAT.
GetUserObjectInformationA | user32.dll | Cette fonction permet de demander les informations à propos d'une station de fenêtre ou d'un objet de bureau.
GetUserObjectInformationW | user32.dll | Cette fonction permet de demander les informations à propos d'une station de fenêtre ou d'un objet de bureau avec des paramètres de chaine de caractères étendues (Unicode).
GetUserObjectSecurity | user32.dll | Cette fonction permet de demander les informations de sécurité pour l'objet utilisateur spécifié.
GetVersion | kernel32.dll | Cette fonction permet de demander le numéro de version courante du système d'exploitation Windows et les informations à propos de la plateforme du système d'exploitation.
GetVersionExA | kernel32.dll | Cette fonction permet de demander la version du système d'exploitation Windows.
GetVersionExW | kernel32.dll | Cette fonction permet de demander la version du système d'exploitation Windows avec des paramètres de chaine de caractères étendue (Unicode).
GetViewportExtEx | gdi32.dll | Cette fonction permet de demander les extensions X et Y d'un point de vue d'un périphérique de contexte.
GetViewportOrgEx | gdi32.dll | Cette fonction permet de demander les coordonnées X et Y d'origine d'une association de point vue avec un périphérique de contexte spécifié.
GetVolumeInformationA | kernel32.dll | Cette fonction permet de demander les informations d'entête d'une unité disque.
GetVolumeInformationW | kernel32.dll | Cette fonction permet de demander les informations d'entête d'une unité disque avec des paramètres de chaine de caractères étendue (Unicode).
GetWindow | user32.dll | Cette fonction permet de demander le Handle d'une fenêtre d'application.
GetWindowContextHelpId | user32.dll | Cette fonction permet de demander l'identificateur de contexte, si possible, associé avec la fenêtre spécifié.
GetWindowDC | user32.dll | Cette fonction permet de demander un périphérique de contexte pour la fenêtre, la barre de titre, les menus et la barre de défilement.
GetWindowLongA | user32.dll | Cette fonction permet de demander, sous forme d'une valeur de 32 bits, une des propriétés d'une fenêtre d'application.
GetWindowLongW | user32.dll | Cette fonction permet de demander, sous forme d'une valeur de 32 bits, une des propriétés d'une fenêtre d'application avec des paramètres de chaine de caractères étendues (Unicode).
GetWindowPlacement | user32.dll | Cette fonction permet de demander l'état d'affichage et de restituer, minimiser et maximiser les positions d'une fenêtre.
GetWindowRect | user32.dll | Cette fonction permet de demander les coordonnées et la taille d'une fenêtre d'application.
GetWindowRgn | user32.dll | Cette fonction permet de demander une copie d'une région de fenêtre dans une fenêtre.
GetWindowsDirectoryA | kernel32.dll | Cette fonction permet de demander le répertoire dans lequel le système d'exploitation Windows est situés.
GetWindowsDirectoryW | kernel32.dll | Cette fonction permet de demander le répertoire dans lequel le système d'exploitation Windows est situés avec des paramètres de chaine de caractères étendue (Unicode).
GetWindowTextA | user32.dll | Cette fonction permet de demander du texte contenu dans une fenêtre d'application.
GetWindowTextLengthA | user32.dll | Cette fonction permet de demander la longueur du texte contenu dans une fenêtre d'application.
GetWindowTextLengthW | user32.dll | Cette fonction permet de demander la longueur du texte contenu dans une fenêtre d'application avec des paramètres de chaine de caractères étendues (Unicode).
GetWindowTextW | user32.dll | Cette fonction permet de demander du texte contenu dans une fenêtre d'application avec des paramètres de chaine de caractères étendues (Unicode).
GetWindowThreadProcessId | user32.dll | Cette fonction permet de demander l'identificateur de processus léger étant créer par la fenêtre spécifié et, optionnellement, l'identificateur du processus créant la fenêtre.
GetWindowWord | user32.dll | Cette fonction permet de demander une valeur 16 bits de déplacement spécifié dans une fenêtre en mémoire supplémentaire pour la fenêtre spécifié.
GetWinMetaFileBits | gdi32.dll | Cette fonction permet de convertir des enregistrements de format étendue en enregistrement de format Windows de fichier méta et d'entreposer les enregistrements convertie dans le tampon spécifié.
GetWindowExtEx | gdi32.dll | Cette fonction permet de demander les extensions X et Y d'une fenêtre associé avec un périphérique de contexte spécifié.
GetWindowOrgEx | gdi32.dll | Cette fonction permet de demander les coordonnées X et Y d'origine de la fenêtre associé avec le périphérique de contexte spécifié.
GetWorldTransform | gdi32.dll | Cette fonction permet de demander l'espace mondiale courant vers la transformation d'espace de page.
glAccum | opengl32.dll | Cette fonction permet d'effecter un contrôle du tampon d'accumulation sous OpenGL.
glAlphaFunc | opengl32.dll | Cette fonction permet de définir la fonction devant servir de test Alpha sous OpenGL.
glBegin | opengl32.dll | Cette fonction permet de définir le commencement de la limite d'une primitive ou d'un groupe de primitive sous OpenGL.
glBitmap | opengl32.dll | Cette fonction permet le contenu d'une image Bitmap sous OpenGL.
glBlendFunc | opengl32.dll | Cette fonction permet de définir les facteurs de blending sous OpenGL.
glCallList | opengl32.dll | Cette fonction permet d'appeler une liste de commande graphique OpenGL.
glCallLists | opengl32.dll | Cette fonction permet d'appeler plusieurs listes de commande graphique OpenGL.
glClear | opengl32.dll | Cette fonction permet de vider les tampons du OpenGL ainsi qu'effacement du contenu de la fenêtre.
glClearAccum | opengl32.dll | Cette fonction permet de vider le tampon d'accumulation du OpenGL.
glClearColor | opengl32.dll | Cette fonction permet de vider le tampon chromatique du OpenGL.
glClearDepth | opengl32.dll | Cette fonction permet de vider le tampon de profondeur du OpenGL.
glClearIndex | opengl32.dll | Cette fonction permet de vider le mode index du OpenGL.
glClearStencil | opengl32.dll | Cette fonction permet de vider le tampon stencil du OpenGL.
glClipPlane | opengl32.dll | Cette fonction permet de définir un nouveau plan de clipping du OpenGL.
glColor3b | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'octet dans OpenGL.
glColor3bv | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'une structure d'octets dans OpenGL.
glColor3d | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'un réel double dans OpenGL.
glColor3dv | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'une structure des réels doubles dans OpenGL.
glColor3f | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'un réel dans OpenGL.
glColor3fv | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'une structure des réels dans OpenGL.
glColor3i | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'un entier long dans OpenGL.
glColor3iv | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'une structure des entiers long dans OpenGL.
glColor3s | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'un entier court dans OpenGL.
glColor3sv | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'une structure des entiers court dans OpenGL.
glEnd | opengl32.dll | Cette fonction permet de définir la fin de la limite d'une primitive ou d'un groupe de primitive sous OpenGL.
GlobalAddAtomA | kernel32.dll | Cette fonction permet d'ajouter un caractère à une table global d'Atom et retourne une valeur unique d'identificateur par chaine de caractères.
GlobalAddAtomW | kernel32.dll | Cette fonction permet d'ajouter un caractère à une table global d'Atom et retourne une valeur unique d'identificateur par chaine de caractères avec des paramètres de chaine de caractères étendue (Unicode).
GlobalAlloc | kernel32.dll | Cette fonction permet d'allouer de la mémoire dans les ressources global du système d'exploitation.
GlobalCompact | kernel32.dll | Cette fonction permet de réorganiser les allocations de mémoire dynamique du tas global en spécifiant une taille de mémoire minimum.
GlobalDeleteAtom | kernel32.dll | Cette fonction permet de décrémenté le compteur de référence de la chaine de caractères d'Atom global.
GlobalFindAtomA | kernel32.dll | Cette fonction permet de rechercher une table d'Atom global dans la chaine de caractères spécifié et demande l'Atom global associé avec la chaine de caractères.
GlobalFindAtomW | kernel32.dll | Cette fonction permet de rechercher une table d'Atom global dans la chaine de caractères spécifié et demande l'Atom global associé avec la chaine de caractères étendue (Unicode).
GlobalFix | kernel32.dll | Cette fonction permet de prévenir l'objet de mémoire global donnée contre un mouvement de mémoire linéaire. Cette fonction est obsolète.
GlobalFlags | kernel32.dll | Cette fonction permet de retourner les informations à propos de l'objet de mémoire global spécifié.
GlobalFree | kernel32.dll | Cette fonction permet de libérer de la mémoire dans les ressources global du système d'exploitation.
GlobalGetAtomNameA | kernel32.dll | Cette fonction permet de demander une copie de la chaine de caractères associé avec l'Atom global donnée.
GlobalGetAtomNameW | kernel32.dll | Cette fonction permet de demander une copie de la chaine de caractères associé avec l'Atom global donnée avec des paramètres de chaine de caractères étendue (Unicode).
GlobalHandle | kernel32.dll | Cette fonction permet de demander le Handle de l'objet de mémoire global spécifié.
GlobalLock | kernel32.dll | Cette fonction permet de verrouiller l'emplacement du bloc de mémoire dans les ressources global du système d'exploitation.
GlobalMemoryStatus | kernel32.dll | Cette fonction permet de demander l'état de la mémoire du système d'exploitation.
GlobalMemoryStatusEx | kernel32.dll | Cette fonction permet de demander l'état de la mémoire du système d'exploitation ayant plus de 4 Go de RAM d'installer sur l'ordinateur.
GlobalReAlloc | kernel32.dll | Cette fonction permet de changer la taille ou les attributs de l'objet de mémoire global spécifié.
GlobalSize | kernel32.dll | Cette fonction permet de demander la taille courante, en octets, de l'objet de mémoire global.
GlobalUnfix | kernel32.dll | Cette fonction permet d'annuler les effets d'une fonction GlobalFix et d'allouer l'objet de mémoire global a être déplacé dans la mémoire linéaire. Cette fonction est obsolète.
GlobalUnlock | kernel32.dll | Cette fonction permet de déverrouiller l'emplacement du bloc de mémoire dans les ressources global du système d'exploitation.
GlobalUnWire | kernel32.dll | Cette fonction permet de restituer la conversion d'un pointeur de processus 32 bits après une allocation de mémoire.
GrayStringA | user32.dll | Cette fonction permet de dessiner du texte grisé à l'emplacement donnée par l'écriture du texte dans un Bitmap en mémoire, grisonnant le Bitmap, et copie alors le Bitmap dans l'affichage.
GrayStringW | user32.dll | Cette fonction permet de dessiner du texte grisé à l'emplacement donnée par l'écriture du texte dans un Bitmap en mémoire, grisonnant le Bitmap, et copie alors le Bitmap dans l'affichage avec des paramètres de chaine de caractères étendues (Unicode).
HeapAlloc | kernel32.dll | Cette fonction permet d'allouer un bloc de mémoire à l'aide d'un tas.
HeapCompact | kernel32.dll | Cette fonction permet de demander la taille de la largeur d'un bloc de mémoire disponible dans le tas spécifié.
HeapCreate | kernel32.dll | Cette fonction permet de créer un objet de tas privé pouvant être utilisé par le processus appelant.
HeapDestroy | kernel32.dll | Cette fonction permet de détruire l'objet de tas spécifié.
HeapFree | kernel32.dll | Cette fonction permet de libérer un bloc de mémoire précédemment alloué d'un tas par la fonction HeapAlloc ou HeapReAlloc.
HeapLock | kernel32.dll | Cette fonction permet de verrouiller l'association avec le tas spécifié dans un objet de section critique.
HeapReAlloc | kernel32.dll | Cette fonction permet de réallouer un bloc de mémoire d'un tas.
HeapSize | kernel32.dll | Cette fonction permet de demander la taille d'un bloc de mémoire alloué dans le tas par la fonction HeapAlloc ou HeapReAlloc.
HeapUnlock | kernel32.dll | Cette fonction permet de déverrouiller l'association avec le tas spécifié dans un objet de section critique.
HeapValidate | kernel32.dll | Cette fonction permet de valider le tas spécifié.
HeapWalk | kernel32.dll | Cette fonction permet d'énumérer les blocs de mémoire dans le tas spécifié.
HideCaret | user32.dll | Cette fonction permet de cacher un caret en l'enlevant de l'écran.
HiliteMenuItem | user32.dll | Cette fonction permet de faire passer un item de menu en mode de surbrillance.
IcmpCloseHandle | icmp.dll | Cette fonction permet de fermer un fichier sur le protocole ICMP (Internet Control Message Protocol).
IcmpCreateFile | icmp.dll | Cette fonction permet de créer un fichier sur le protocole ICMP (Internet Control Message Protocol).
IcmpSendEcho | icmp.dll | Cette fonction permet d'envoyer une requête en écho par le protocole ICMP (Internet Control Message Protocol).
ImpersonateDdeClientWindow | user32.dll | Cette fonction permet d'activer l'échange de données dynamique (DDE) vers un contexte d'application sécurisé d'imitation de client DDE.
ImpersonateLoggedOnUser | advapi32.dll | Cette fonction permet d'appeler une processus léger utilisateur impersonnel.
ImpersonateNamedPipeClient | advapi32.dll | Cette fonction permet de demmander si l'application client a un nom de canal impersonnel.
ImpersonateSelf | advapi32.dll | Cette fonction permet de demander le jeton d'accès au contexte de sécurité impersonnel d'un processus appelant.
inet_addr | Wsock32.dll | Cette fonction permet l'adresse IP contenu dans une chaine de caractères sous sa forme numérique 32 bits.
InflateRect | user32.dll | Cette fonction permet d'agrandir ou diminuer la taille d'un objet de rectangle.
InitAtomTable | kernel32.dll | Cette fonction permet d'initialiser la table d'Atom local et fixe la taille spécifié.
InitCommonControlsEx | comctl32.dll | Cette fonction permet d'initialiser une classe de contrôle commun.
InitializeAcl | advapi32.dll | Cette fonction permet de créer une nouvelle structure de liste de contrôle d'accès (ACL).
InitializeCriticalSection | kernel32.dll | Cette fonction permet d'initialiser un objet de section critique.
InitializeSecurityDescriptor | advapi32.dll | Cette fonction permet d'initialiser un nouveau descripteur de sécurité.
InitializeSid | advapi32.dll | Cette fonction permet d'initialiser une structure d'identificateur de sécurité (SID).
InitiateSystemShutdownA | advapi32.dll | Cette fonction permet d'initier l'opération de fermeture ou de redémarrage en cours d'exécution du système d'exploitation Windows.
InitiateSystemShutdownW | advapi32.dll | Cette fonction permet d'initier l'opération de fermeture ou de redémarrage en cours d'exécution du système d'exploitation Windows avec des paramètres de chaine de caractères étendue (Unicode).
InSendMessage | user32.dll | Cette fonction permet de spécifier si la procédure de la fenêtre courante est en traitement d'un message d'envoi d'une autre tâche par appel de la fonction SendMessage.
InsertMenuA | user32.dll | Cette fonction permet d'insérer un nouvel item de menu dans un menu et déplace les autres items en bas du menu.
InsertMenuItemA | user32.dll | Cette fonction permet d'insérer un nouvel item de menu à la position spécifié dans la barre de menu ou un menu contextuel.
InsertMenuItemW | user32.dll | Cette fonction permet d'insérer un nouvel item de menu à la position spécifié dans la barre de menu ou un menu contextuel avec les paramètres de chaine de caractères étendues (Unicode).
InsertMenuW | user32.dll | Cette fonction permet d'insérer un nouvel item de menu dans un menu et déplace les autres items en bas du menu avec des paramètres de chaine de caractères étendues (Unicode).
InstallNewDevice | newdev.dll | Cette fonction permet d'installer un nouveau périphérique.
InterlockedCompareExchange | kernel32.dll | Cette fonction permet d'effectuer un traitement de comparaison atomique de valeurs spécifiés et échange les valeurs en se basant sur le résultat de la comparaison.
InterlockedDecrement | kernel32.dll | Cette fonction permet de décrémenter les deux valeurs spécifié de un et vérifie le résultat des valeurs.
InterlockedExchange | kernel32.dll | Cette fonction permet d'échanger automatiquement une paire de valeurs 32 bits.
InterlockedExchangeAdd | kernel32.dll | Cette fonction permet d'effectuer un traitement automatique d'ajouter d'une valeur incrémenté à une variable ajouté.
InterlockedIncrement | kernel32.dll | Cette fonction permet d'incrémenter les deux valeurs des variables spécifiés et vérifie la valeur du résultat.
InternetAutodial | wininet.dll | Cette fonction permet de provoquer une connexion Internet en utilisant le protocole TCP/IP.
InternetAutodialHangup | wininet.dll | Cette fonction permet d'arrêter une connexion Internet en utilisant le protocole TCP/IP.
InternetGetConnectedState | wininet.dll | Cette fonction permet de demander le type de connexion Internet qu'utilise le TCP/IP de Windows.
IntersectClipRect | gdi32.dll | Cette fonction permet de créer une nouvelle région de capture vers l'intersection de la région courante et du rectangle spécifié.
IntersectRect | user32.dll | Cette fonction permet de calculer l'intersection de 2 sources rectangulaires et place les coordonnées du rectangle d'intersection dans le rectangle de destination.
InvalidateRect | user32.dll | Cette fonction permet d'ajouter un rectangle pour la mise à jour d'une région d'une fenêtre.
InvalidateRgn | user32.dll | Cette fonction permet d'ajouter une région à une région de fenêtre à mettre à jour.
InvertRect | user32.dll | Cette fonction permet d'inverser une région rectangulaire.
InvertRgn | gdi32.dll | Cette fonction permet d'inverser les couleurs d'une région spécifié.
IsBadCodePtr | kernel32.dll | Cette fonction permet de demander si l'appel du processus a un accès de mémoire en lecture à l'adresse spécifié.
IsBadHugeReadPtr | kernel32.dll | Cette fonction permet de vérifier si l'appel du processus a un accès en lecture dans les rangs de mémoire spécifié.
IsBadHugeWritePtr | kernel32.dll | Cette fonction permet de vérifier si l'appel du processus a un accès en écriture dans les rangs de mémoire spécifié.
IsBadReadPtr | kernel32.dll | Cette fonction permet de vérifier si l'appel du processus a un accès en lecture dans les rangs de mémoire spécifié.
IsBadStringPtrA | kernel32.dll | Cette fonction permet de vérifier que l'appel du processus à un accès en lecture dans les rangs de mémoire pointer sur une chaine de caractères.
IsBadStringPtrW | kernel32.dll | Cette fonction permet de vérifier que l'appel du processus à un accès en lecture dans les rangs de mémoire pointer sur une chaine de caractères étendue (Unicode).
IsBadWritePtr | kernel32.dll | Cette fonction permet de vérifier si l'appel du processus a un accès en écriture dans les rangs de mémoire spécifié.
IsDBCSLeadByte | kernel32.dll | Cette fonction permet de demander si un caractère spécifié est un octet de tête, le premier octet d'un caractère est un DBCS (Double-Byte Character Set).
IsDBCSLeadByteEx | kernel32.dll | Cette fonction permet de demander si un caractère spécifié est un octet de tête, le premier octet d'un caractère est un DBCS (Double-Byte Character Set) avec un code de page.
IsCharAlphaA | user32.dll | Cette fonction permet de demander si un caractère fait partie de l'ensemble de caractères alphabétiques de la langue.
IsCharAlphaNumericA | user32.dll | Cette fonction permet de demander si un caractère est une lettre ou un chiffre arabes.
IsCharAlphaNumericW | user32.dll | Cette fonction permet de demander si un caractère est une lettre ou un chiffre arabes avec des paramètres de chaine de caractères étendues (Unicode).
IsCharAlphaW | user32.dll | Cette fonction permet de demander si un caractère fait partie de l'ensemble de caractères alphabétiques de la langue avec des paramètres de chaine de caractères étendues (Unicode).
IsCharLowerA | user32.dll | Cette fonction permet de demander si un caractère est une lettre en minuscule.
IsCharLowerW | user32.dll | Cette fonction permet de demander si un caractère est une lettre en minuscule avec des paramètres de chaine de caractères étendues (Unicode).
IsCharUpperA | user32.dll | Cette fonction permet de demander si un caractère est une lettre en majuscule.
IsCharUpperW | user32.dll | Cette fonction permet de demander si un caractère est une lettre en majuscule avec des paramètres de chaine de caractères étendues (Unicode).
IsChild | user32.dll | Cette fonction permet de demander si la fenêtre spécifié est l'enfant ou d'une autre descendance direct de la fenêtre parent spécifié.
IsClipboardFormatAvailable | user32.dll | Cette fonction permet de demander si les données d'un certain format existe dans le presse-papier.
IsDebuggerPresent | kernel32.dll | Cette fonction permet de demander si l'application en cours est sous l'influence d'un débogueur.
IsDialogMessageA | user32.dll | Cette fonction permet de demander si le message spécifié est prévu dans la modélisation de la boite de dialogue donnée et, si c'est le cas, traite le message.
IsDialogMessageW | user32.dll | Cette fonction permet de demander si le message spécifié est prévu dans la modélisation de la boite de dialogue donnée et, si c'est le cas, traite le message avec des paramètres de chaine de caractères étendues (Unicode).
IsDlgButtonChecked | user32.dll | Cette fonction permet de demander si un bouton a une coche à côté de lui et si un bouton à trois états de gris, est coché ou pas.
IsIconic | user32.dll | Cette fonction permet de demander si la fenêtre spécifié est minimisé (en état d'icône).
IsMenu | user32.dll | Cette fonction permet d'indiquer si le Handle spécifié est un Handle de menu.
IsProcessorFeaturePresent | kernel32.dll | Cette fonction permet de demander si les fonctionnalités du microprocessseur sont supportés par l'un des microprocesseur de la machine courante.
IsRectEmpty | user32.dll | Cette fonction permet de déterminer si le rectangle spécifié est vide.
IsTextUnicode | advapi32.dll | Cette fonction permet de demander l'adresse du tampon contenant probablement une forme de texte Unicode.
IsValidAcl | advapi32.dll | Cette fonction permet de valider une liste de contrôle d'accès (ACL).
IsValidCodePage | kernel32.dll | Cette fonction permet d'indiquer si la page de code spécifié est valide.
IsValidLocale | kernel32.dll | Cette fonction permet d'appliquer un test de validité à un identificateur locale.
IsValidSecurityDescriptor | advapi32.dll | Cette fonction permet de valider une structure de descripteur de sécurité «SECURITY_DESCRIPTOR».
IsValidSid | advapi32.dll | Cette fonction permet de valider une structure SID en vérifiant que le numéro de version est situé dans le rang et les intervalles de nombres de sous-autorité.
IsWindow | user32.dll | Cette fonction permet de demander si la fenêtre de Handle spécifié est valide.
IsWindowEnabled | user32.dll | Cette fonction permet de demander si la fenêtre spécifié est activé par la souris ou l'entrée clavier.
IsWindowUnicode | user32.dll | Cette fonction permet de demander si la fenêtre spécifié est une fenêtre Unicode native.
IsWindowVisible | user32.dll | Cette fonction permet de demander si la fenêtre spécifié est dans un état de visibilité.
IsZoomed | user32.dll | Cette fonction permet de demander si la fenêtre spécifié est maximisé (ou prend toute l'écran).
keybd_event | user32.dll | Cette fonction permet de provoquer un événement d'appui de touche clavier.
KillTimer | user32.dll | Cette fonction permet de détruire la minuterie spécifié.
LCMapStringA | kernel32.dll | Cette fonction permet de cartographier une chaine de caractères vers une autre en effectuant le traitement de transformation de dépendance locale.
LCMapStringW | kernel32.dll | Cette fonction permet de cartographier une chaine de caractères étendue (Unicode) vers une autre en effectuant le traitement de transformation de dépendance locale.
LeaveCriticalSection | kernel32.dll | Cette fonction permet de libérer le propriétaire d'un objet de section critique spécifié.
LineDDA | gdi32.dll | Cette fonction permet de calculer tous les points successifs d'une ligne spécifié par les coordonnées de début et de fin.
LineTo | gdi32.dll | Cette fonction permet de tracer une ligne des coordonnées courante à la position spécifié.
LoadAcceleratorsA | user32.dll | Cette fonction permet d'effectuer le chargement d'une table d'accélérateur d'une ressource.
LoadAcceleratorsW | user32.dll | Cette fonction permet d'effectuer le chargement d'une table d'accélérateur d'une ressource avec des paramètres de chaine de caractères étendues (Unicode).
LoadBitmapA | user32.dll | Cette fonction permet de charger la ressource de Bitmap spécifié du fichier de module d'exécutable donnée.
LoadBitmapW | user32.dll | Cette fonction permet de charger la ressource de Bitmap spécifié du fichier de module d'exécutable donnée avec des paramètres de chaine de caractères étendues (Unicode).
LoadCursorA | user32.dll | Cette fonction permet de sélectionner un pointeur de souris.
LoadCursorFromFileA | user32.dll | Cette fonction permet de charger à partir d'un fichier un pointeur de souris.
LoadCursorFromFileW | user32.dll | Cette fonction permet de charger à partir d'un fichier un pointeur de souris avec des paramètres de chaine de caractères étendues (Unicode).
LoadCursorW | user32.dll | Cette fonction permet de sélectionner un pointeur de souris avec des paramètres de chaine de caractères étendues (Unicode).
LoadIconA | user32.dll | Cette fonction permet de charger une icône à partir d'un fichier.
LoadIconW | user32.dll | Cette fonction permet de charger une icône à partir d'un fichier avec des paramètres de chaine de caractères étendues (Unicode).
LoadImageA | user32.dll | Cette fonction permet de charger une image à partir d'une icône, un curseur ou un Bitmap.
LoadImageW | user32.dll | Cette fonction permet de charger une image à partir d'une icône, un curseur ou un Bitmap avec des paramètres de chaine de caractères étendues (Unicode).
LoadKeyboardLayoutA | user32.dll | Cette fonction permet de charger une nouvelle couche clavier dans le système.
LoadKeyboardLayoutW | user32.dll | Cette fonction permet de charger une nouvelle couche clavier dans le système avec des paramètres de chaine de caractères étendues (Unicode).
LoadLibraryA | kernel32.dll | Cette fonction permet d'effectuer le chargement du module de bibliothèque spécifié.
LoadLibraryW | kernel32.dll | Cette fonction permet d'effectuer le chargement du module de bibliothèque spécifié avec des paramètres de chaine de caractères étendue (Unicode).
LoadLibraryExA | kernel32.dll | Cette fonction permet de cartographier un module exécutable spécifié dans l'espace d'adressage du processus d'appel.
LoadLibraryExW | kernel32.dll | Cette fonction permet de cartographier un module exécutable spécifié dans l'espace d'adressage du processus d'appel avec des paramètres de chaine de caractères étendue (Unicode).
LoadMenuA | user32.dll | Cette fonction permet d'effectuer le chargement de la ressource de menu spécifié du fichier exécutable avec l'instance d'application donnée.
LoadMenuIndirectA | user32.dll | Cette fonction permet de charger le gabarit de menu spécifié en mémoire.
LoadMenuIndirectW | user32.dll | Cette fonction permet de charger le gabarit de menu spécifié en mémoire avec des paramètres de chaine de caractères étendues (Unicode).
LoadMenuW | user32.dll | Cette fonction permet d'effectuer le chargement de la ressource de menu spécifié du fichier exécutable avec l'instance d'application donnée avec des paramètres de chaine de caractères étendues (Unicode).
LoadModule | kernel32.dll | Cette fonction permet de charger et d'exécution une application Windows ou crée un nouvel instance dans un application Windows existante.
LoadResource | kernel32.dll | Cette fonction permet de charger la ressource spécifié dans la mémoire global.
LoadStringA | user32.dll | Cette fonction permet d'effectuer le chargement de la chaine de caractères spécifié d'une ressource.
LoadStringW | user32.dll | Cette fonction permet d'effectuer le chargement de la chaine de caractères spécifié d'une ressource avec des paramètres de chaine de caractères étendues (Unicode).
LocalAlloc | kernel32.dll | Cette fonction permet d'allouer le nombre d'octets spécifiés pour le tas local.
LocalCompact | kernel32.dll | Cette fonction permet de réarranger le tas local si la quantité de mémoire spécifié est disponible.
LocalFileTimeToFileTime | kernel32.dll | Cette fonction permet de convertir un temps de fichier local en temps de fichier basé sur l'UTC (Coordinated Universal Time).
LocalFlags | kernel32.dll | Cette fonction permet de demander les informations à propos de l'objet de mémoire local.
LocalFree | kernel32.dll | Cette fonction permet de demander l'objet de mémoire local et d'invalider un Handle.
LocalLock | kernel32.dll | Cette fonction permet de demander un pointeur sur l'objet de mémoire local.
LocalReAlloc | kernel32.dll | Cette fonction permet de changer la taille ou les attributs d'un objet de mémoire local spécifié.
LocalShrink | kernel32.dll | Cette fonction rapetisser le tas local du segment spécifié.
LocalSize | kernel32.dll | Cette fonction permet de demander la taille, en octets, de l'objet de mémoire local spécifié.
LocalUnlock | kernel32.dll | Cette fonction permet de déverrouiller un objet de mémoire local spécifié.
LockFile | kernel32.dll | Cette fonction permet de verrouiller une région d'un fichier ouvert.
LockFileEx | kernel32.dll | Cette fonction permet de verrouiller un rang d'octets dans un fichier ouvert avec un accès de partage ou d'exclusivité.
LockResource | kernel32.dll | Cette fonction permet de verrouiller une ressource spécifié.
LockWindowUpdate | user32.dll | Cette fonction permet d'activer ou désactiver l'affichage du contenu d'une fenêtre d'application.
LockWorkStation | user32.dll | Cette fonction permet verrouiller la poste de travail avec le mot de passe de l'utilisateur courant. Cette fonction est l'équivalent de Ctrl+Alt+Delete et «Verrouiller Station de Travail».
LogonUserA | advapi32.dll | Cette fonction permet d'effectuer une opération de connexion d'un utilisateur.
LogonUserW | advapi32.dll | Cette fonction permet d'effectuer une opération de connexion d'un utilisateur avec des paramètres de chaine de caractères étendue (Unicode).
LookupAccountNameA | advapi32.dll | Cette fonction permet d'accepter le nom d'un système ou d'un compte en entrée.
LookupAccountNameW | advapi32.dll | Cette fonction permet d'accepter le nom d'un système ou d'un compte en entrée avec des paramètres de chaine de caractères étendue (Unicode).
LookupAccountSidA | advapi32.dll | Cette fonction permet d'accepter un identificateur de sécurité (SID) en entrée
LookupAccountSidW | advapi32.dll | Cette fonction permet d'accepter un identificateur de sécurité (SID) en entrée avec des paramètres de chaine de caractères étendue (Unicode).
LookupIconIdFromDirectory | user32.dll | Cette fonction permet d'effectuer la recherche de données d'icône ou de curseur dans un icône ou curseur étant la meilleure correspondance avec le périphérique d'affichage courant.
LookupIconIdFromDirectoryEx | user32.dll | Cette fonction permet d'effectuer la recherche de données d'icône ou de curseur dans un icône ou curseur étant la meilleure correspondance avec le périphérique d'affichage courant avec des options particulières.
LookupPrivilegeDisplayNameA | advapi32.dll | Cette fonction permet de demander le nom d'affichage représenté par le privilège spécifié.
LookupPrivilegeDisplayNameW | advapi32.dll | Cette fonction permet de demander le nom d'affichage représenté par le privilège spécifié avec des paramètres de chaine de caractères étendue (Unicode).
LookupPrivilegeNameA | advapi32.dll | Cette fonction permet de demander le nom correspondant au privilège représenté par un système spécifié par identificateur local unique (LUID).
LookupPrivilegeNameW | advapi32.dll | Cette fonction permet de demander le nom correspondant au privilège représenté par un système spécifié par identificateur local unique (LUID) avec des paramètres de chaine de caractères étendue (Unicode)
LookupPrivilegeValueA | advapi32.dll | Cette fonction permet de demander l'identificateur local unique (LUID) utilisé par un système spécifié par une représentation local d'un nom de prévilège.
LookupPrivilegeValueW | advapi32.dll | Cette fonction permet de demander l'identificateur local unique (LUID) utilisé par un système spécifié par une représentation local d'un nom de prévilège avec des paramètres de chaine de caractères étendue (Unicode)
LPtoDP | gdi32.dll | Cette fonction permet de convertir les coordonnées logiques (points) en coordonnées d'un périphérique.
lstrcatA | kernel32.dll | Cette fonction permet d'ajouter une chaine de caractères à une autre.
lstrcatW | kernel32.dll | Cette fonction permet d'ajouter une chaine de caractères étendue (Unicode) à une autre.
lstrcmpA | kernel32.dll | Cette fonction permet d'effectuer la comparaison de deux chaines de caractères ASCIZ.
lstrcmpW | kernel32.dll | Cette fonction permet d'effectuer la comparaison de deux chaines de caractères étendues (Unicode).
lstrcmpiA | kernel32.dll | Cette fonction permet d'effectuer une comparaison de deux chaines de caractères sans tenir compte des majuscules ou minuscules.
lstrcmpiW | kernel32.dll | Cette fonction permet d'effectuer une comparaison de deux chaines de caractères étendues (Unicode) sans tenir compte des majuscules ou minuscules.
lstrcpyA | kernel32.dll | Cette fonction permet de copier une chaine de caractères dans un tampon.
lstrcpyW | kernel32.dll | Cette fonction permet de copier une chaine de caractères étendues (Unicode) dans un tampon.
lstrcpynA | kernel32.dll | Cette fonction permet d'effectuer une copie du nombre de caractères spécifié d'une chaine de caractères vers un tampon en mémoire.
lstrcpynW | kernel32.dll | Cette fonction permet d'effectuer une copie du nombre de caractères spécifié d'une chaine de caractères étendue (Unicode) vers un tampon en mémoire.
lstrlenA | kernel32.dll | Cette fonction permet de retourner la longueur, en octets, de la chaine de caractères spécifié.
lstrlenW | kernel32.dll | Cette fonction permet de retourner la longueur, en octets, de la chaine de caractères étendue (Unicode) spécifié.
LZClose | lz32.dll | Cette fonction permet de fermer un fichier compressé.
LZCopy | lz32.dll | Cette fonction permet de copier un fichier en dehors du fichier compressé ou permet de le compresser et de l'inclure avec le fichier compressé.
LZOpenFileA | lz32.dll | Cette fonction permet de créer, ouvrir ou supprimer un fichier compressé.
MakeAbsoluteSD | advapi32.dll | Cette fonction permet de créer un descripteur de sécurité dans un format absolue en utilisant un gabarit de format relatif de descripteur de sécurité.
MakeSelfRelativeSD | advapi32.dll | Cette fonction permet de créer un descripteur de sécurité dans un format relatif en utilisant un gabarit de format absolue de descripteur de sécurité.
MakeSureDirectoryPathExists | imagehlp.dll | Cette fonction permet de s'assurer que toutes l'arborescente d'un chemin de répertoires existent et ont été créés.
MapDialogRect | user32.dll | Cette fonction permet d'effectuer la conversion de l'unité de boite de dialogue à l'unité d'écran (pixels).
MapGenericMask | advapi32.dll | Cette fonction permet de générer une carte de masque de bon accès spécifié et de bon accès standard
MAPISendDocuments | mapi32.dll | Cette fonction permet d'effectuer l'envoie d'un message de style standard avec des fichiers attachés.
MapViewOfFile | kernel32.dll | Cette fonction permet de cartographier une vue d'un fichier dans l'espace d'adressage d'un processus appelant.
MapViewOfFileEx | kernel32.dll | Cette fonction permet de cartographier une vue d'un fichier dans l'espace d'adressage d'un processus appelant avec un adresse de mémoire spécifié.
MapVirtualKeyA | user32.dll | Cette fonction permet d'effectuer la conversion du code de touche virtuel en une valeur de caractère et un code de balayage (scan code) ou convertie le code de balayage en code de touche virtuel.
MapVirtualKeyExA | user32.dll | Cette fonction permet d'effectuer la conversion du code de touche virtuel en une valeur de caractère et un code de balayage (scan code) ou convertie le code de balayage en code de touche virtuel en utilisant des options supplémentaires.
MapVirtualKeyExW | user32.dll | Cette fonction permet d'effectuer la conversion du code de touche virtuel en une valeur de caractère et un code de balayage (scan code) ou convertie le code de balayage en code de touche virtuel en utilisant des options supplémentaires avec des paramètres de chaine de caractères étendues (Unicode).
MapVirtualKeyW | user32.dll | Cette fonction permet d'effectuer la conversion du code de touche virtuel en une valeur de caractère et un code de balayage (scan code) ou convertie le code de balayage en code de touche virtuel avec des paramètres de chaine de caractères étendues (Unicode).
MapWindowPoints | user32.dll | Cette fonction permet de convertir (cartographié) un ensemble de points d'une espace de coordonnées relatif d'une fenêtre vers une autre fenêtre d'espace de coordonnées relatif.
MaskBlt | gdi32.dll | Cette fonction permet de fusionner les données de couleur du Bitmap de la source et la destination en utilisant les opérations de masque et de raster spécifié.
mciSendStringA | winmm.dll | Cette fonction permet d'envoyer des commandes au pilote matériel MCI s'occupant du multimédia.
MenuItemFromPoint | user32.dll | Cette fonction permet de demander quel est l'item de menu situé à l'emplacement spécifié, peut importe lequel.
MessageBeep | user32.dll | Cette fonction permet de faire jouer un de ses sons standard du système d'exploitation Windows.
MessageBoxA | user32.dll | Cette fonction permet d'afficher une boite de dialogue contenant un message et des boutons d'actions.
MessageBoxExA | user32.dll | Cette fonction permet de créer, d'afficher et d'effectuer des opérations sur une boite de message.
MessageBoxExW | user32.dll | Cette fonction permet de créer, d'afficher et d'effectuer des opérations sur une boite de message avec des paramètres de chaine de caractères étendues (Unicode).
MessageBoxIndirectA | user32.dll | Cette fonction permet de créer, d'afficher et d'effectuer des opérations sur une boite de message à l'aide d'une structure de données.
MessageBoxIndirectW | user32.dll | Cette fonction permet de créer, d'afficher et d'effectuer des opérations sur une boite de message à l'aide d'une structure de données avec des paramètres de chaine de caractères étendues (Unicode).
MessageBoxW | user32.dll | Cette fonction permet d'afficher une boite de dialogue contenant un message et des boutons d'actions avec des paramètres de chaine de caractères étendues (Unicode).
ModifyMenuA | user32.dll | Cette fonction permet d'effectuer une modification sur un item de menu existant.
ModifyMenuW | user32.dll | Cette fonction permet d'effectuer une modification sur un item de menu existant avec des paramètres de chaine de caractères étendues (Unicode).
ModifyWorldTransform | gdi32.dll | Cette fonction permet de changer la transformation mondiale pour le périphérique de contexte utilisant le mode spécifié.
mouse_event | user32.dll | Cette fonction permet de provoquer un événement de déplacement de pointeur de souris ou d'actions de bouton de souris.
MoveFileA | kernel32.dll | Cette fonction permet de renommer ou déplacer un fichier ou un répertoire
MoveFileExA | kernel32.dll | Cette fonction permet de renommer un fichier existant.
MoveFileExW | kernel32.dll | Cette fonction permet de renommer un fichier existant avec un paramètre de chaine de caractères étendue (Unicode).
MoveFileW | kernel32.dll | Cette fonction permet de renommer ou déplacer un fichier ou un répertoire avec un paramètre de chaine de caractères étendue (Unicode).
MoveToEx | gdi32.dll | Cette fonction permet de changer la position du pointeur graphique
MoveWindow | user32.dll | Cette fonction permet de changer l'emplacement d'une fenêtre d'application
MsgWaitForMultipleObjects | user32.dll | Cette fonction permet de retourner un objet spécifié suite à un état, un type spécifié étant disponible de l'entrée de la file d'attente d'un processus léger ou intervalle de dépassement de délai.
MsgWaitForMultipleObjectsEx | user32.dll | Cette fonction permet de retourner un objet spécifié suite à un état, un type spécifié étant disponible de l'entrée de la file d'attente d'un processus léger ou intervalle de dépassement de délai avec des paramètres supplémentaires.
MulDiv | kernel32.dll | Cette fonction permet de diviser le produit de deux entiers long
MultiByteToWideChar | kernel32.dll | Cette fonction permet de convertir une chaine de caractères ASCII en chaine de caractères Unicode.
MultinetGetConnectionPerformanceA | mpr.dll | Cette fonction permet de demander les informations à propos des performances attendu d'une connexion utilisé pour l'accès à une ressource de réseau.
MultinetGetConnectionPerformanceW | mpr.dll | Cette fonction permet de demander les informations à propos des performances attendu d'une connexion utilisé pour l'accès à une ressource de réseau avec des paramètres de chaine de caractères étendues (Unicode).
NotifyChangeEventLog | advapi32.dll | Cette fonction permet de recevoir des avertissements d'application lorsqu'un événement écrit dans le fichier de journal de bord spécifié.
ObjectCloseAuditAlarmA | advapi32.dll | Cette fonction permet de généré des messages dans le journal de bord quand un identificateur Handle d'objet est supprimé.
ObjectCloseAuditAlarmW | advapi32.dll | Cette fonction permet de généré des messages dans le journal de bord quand un identificateur Handle d'objet est supprimé avec des paramètres de chaine de caractères étendue (Unicode).
ObjectDeleteAuditAlarmA | advapi32.dll | Cette fonction permet de supprimer un message dans le journal de bord.
ObjectDeleteAuditAlarmW | advapi32.dll | Cette fonction permet de supprimer un message dans le journal de bord avec des paramètres de chaine de caractères étendue (Unicode).
ObjectOpenAuditAlarmA | advapi32.dll | Cette fonction permet de générer des messages dans le journal de bord quand une application client tente d'obtenir l'accès à un objet ou dans créer un nouveau.
ObjectOpenAuditAlarmW | advapi32.dll | Cette fonction permet de générer des messages dans le journal de bord quand une application client tente d'obtenir l'accès à un objet ou dans créer un nouveau avec des paramètres de chaine de caractères étendue (Unicode).
ObjectPrivilegeAuditAlarmA | advapi32.dll | Cette fonction permet de générer un message dans le journal de bord quand un résultat d'un client est effectué avec des opérations de privilège sur un serveur d'application déjà ouvert avec l'identificateur Handle de l'objet.
ObjectPrivilegeAuditAlarmW | advapi32.dll | Cette fonction permet de générer un message dans le journal de bord quand un résultat d'un client est effectué avec des opérations de privilège sur un serveur d'application déjà ouvert avec l'identificateur Handle de l'objet avec des paramètres de chaine de caractères étendue (Unicode).
OemKeyScan | user32.dll | Cette fonction permet de demander le code ASCII OEM entre 0 et FFh situé dans les codes de balayage et des touches états (Ctrl, Alt et Shift).
OemToCharA | user32.dll | Cette fonction permet d'effectuer la conversion d'une chaine de caractères contenant un ensemble de caractères OEM vers du ANSI ou du Unicode.
OemToCharBuffA | user32.dll | Cette fonction permet d'effectuer la conversion d'un certain nombre de caractères OEM vers du ANSI ou du Unicode.
OemToCharBuffW | user32.dll | Cette fonction permet d'effectuer la conversion d'un certain nombre de caractères OEM vers du ANSI ou du Unicode avec des paramètres de chaine de caractères étendues (Unicode).
OemToCharW | user32.dll | Cette fonction permet d'effectuer la conversion d'une chaine de caractères contenant un ensemble de caractères OEM vers du ANSI ou du Unicode avec des paramètres de chaine de caractères étendues (Unicode).
OffsetClipRgn | gdi32.dll | Cette fonction permet de déplacer la région de clip dans le périphérique spécifié et par le déplacement spécifié.
OffsetRect | user32.dll | Cette fonction permet de déplacer le rectangle spécifié avec le nombre de déplacements spécifié.
OffsetRgn | gdi32.dll | Cette fonction permet de déplacer la région spécifié par le déplacement spécifié.
OffsetViewportOrgEx | gdi32.dll | Cette fonction permet de modifier le point de vue d'origine relatif pour un périphérique de contexte en utilisant le déplacement horizontal et vertical spécifié.
OffsetWindowOrgEx | gdi32.dll | Cette fonction permet de modifier le point de vue de la fenêtre d'origine relatif de la valeur courante.
OpenBackupEventLogA | advapi32.dll | Cette fonction permet d'ouvrir un identificateur Handle pour un événement de copie de sauvegarde du journal de bord.
OpenBackupEventLogW | advapi32.dll | Cette fonction permet d'ouvrir un identificateur Handle pour un événement de copie de sauvegarde du journal de bord avec des paramètres de chaine de caractères étendue (Unicode).
OpenClipboard | user32.dll | Cette fonction permet de commencer un traitement avec le presse-papier.
OpenDesktopA | user32.dll | Cette fonction permet de demander le Handle d'un bureau existant.
OpenDesktopW | user32.dll | Cette fonction permet de demander le Handle d'un bureau existant avec des paramètres de chaine de caractères étendues (Unicode).
OpenEventA | kernel32.dll | Cette fonction permet de demander un Handle dans objet existant d'événement nommé.
OpenEventW | kernel32.dll | Cette fonction permet de demander un Handle dans objet existant d'événement nommé avec des paramètres de chaines de caractères étendue (Unicode).
OpenEventLogA | advapi32.dll | Cette fonction permet d'ouvrir un identificateur Handle d'un événement de journal de bord.
OpenEventLogW | advapi32.dll | Cette fonction permet d'ouvrir un identificateur Handle d'un événement de journal de bord avec des paramètres de chaine de caractères étendue (Unicode).
OpenFile | kernel32.dll | Cette fonction permet de créer, d'ouvrir, de réouvrir ou de supprimer un fichier.
OpenFileMappingA | kernel32.dll | Cette fonction permet d'ouvrir un objet de cartographie de fichier nommé.
OpenFileMappingW | kernel32.dll | Cette fonction permet d'ouvrir un objet de cartographie de fichier nommé avec des paramètres de chaine de caractères étendue (Unicode).
OpenIcon | user32.dll | Cette fonction permet de passer en mode actif l'icône d'une fenêtre.
OpenInputDesktop | user32.dll | Cette fonction permet de demander le Handle d'un bureau recevant une entrée utilisateur.
OpenMutexA | kernel32.dll | Cette fonction permet d'ouvrir un objet Mutex nommé.
OpenMutexW | kernel32.dll | Cette fonction permet d'ouvrir un objet Mutex nommé avec des paramètres de chaine de caractères étendue (Unicode).
OpenPrinterA | winspool.drv | Cette fonction permet d'ouvrir la communication avec une imprimante.
OpenProcess | kernel32.dll | Cette fonction permet d'ouvrir un accès de jeton associé avec un processus.
OpenProcessToken | advapi32.dll | Cette fonction permet d'ouvrir un accès de jeton associé avec un processus.
OpenSemaphoreA | kernel32.dll | Cette fonction permet de retourner le Handle d'objet existant de sémaphore nommé.
OpenSemaphoreW | kernel32.dll | Cette fonction permet de retourner le Handle d'objet existant de sémaphore nommé avec des paramètres de chaine de caractères étendue (Unicode).
OpenThreadToken | advapi32.dll | Cette fonction permet d'ouvrir un accès de jeton associé avec un processus léger.
OpenWaitableTimerA | kernel32.dll | Cette fonction permet d'ouvrir une minuterie d'attente existante nommé.
OpenWaitableTimerW | kernel32.dll | Cette fonction permet d'ouvrir une minuterie d'attente existante nommé avec des paramètres de chaine de caractères étendue (Unicode).
OpenWindowStationA | user32.dll | Cette fonction permet de demander le Handle d'une fenêtre de station existante.
OpenWindowStationW | user32.dll | Cette fonction permet de demander le Handle d'une fenêtre de station existante avec des paramètres de chaine de caractères étendues (Unicode).
OutputDebugStringA | kernel32.dll | Cette fonction permet d'envoyer une chaine de caractères au débogueur de l'application courante.
OutputDebugStringW | kernel32.dll | Cette fonction permet d'envoyer une chaine de caractères au débogueur de l'application courante avec des paramètres de chaine de caractères étendue (Unicode).
PackDDElParam | user32.dll | Cette fonction permet de compacté la valeur DDE spécifié dans une structure interne utilisé dans le partage de données DDE entre le traitement de processus.
PaintDesktop | user32.dll | Cette fonction permet de remplir le bureau avec une couleur ou un papier-peint.
PaintRgn | gdi32.dll | Cette fonction permet de remplir une région, en utilisant la brosse courante, pour un périphérique de contexte spécifié.
PatBlt | gdi32.dll | Cette fonction permet de créer une palette de bits pour un périphérique spécifié.
PathAddBackslashA | shlwapi.dll | Cette fonction permet d'ajouter une barre oblique inversé à la fin du chemin de répertoire spécifié.
PathAddExtension | shlwapi.dll | Cette fonction permet d'ajouter une extension à nom de fichiers.
PathAppendA | shlwapi.dll | Cette fonction permet d'ajouter un chemin de répertoire à la fin d'un autre chemin de répertoire.
PathBuildRootA | shlwapi.dll | Cette fonction permet de créer un répertoire racine pour unité disque spécifié.
PathCanonicalizeA | shlwapi.dll | Cette fonction permet de transformer un chemin de répertoire de sa forme absolue à relative, c'est-à-dire de remplacer des "../" pour atteinte le répertoire en court par exemple.
PathCombine | shlwapi.dll | Cette fonction permet d'ajouter un chemin de répertoire à la fin d'un autre chemin de répertoire.
PathCommonPrefix | shlwapi.dll | Cette fonction permet d'effectuer la comparaison de deux répertoires et de vérifier les préfixes communs.
PathCompactPathA | shlwapi.dll | Cette fonction permet de réduire la longueur en pixel d'un chemin de répertoire en remplaçant la partie trop longue par des abréviations pour qu'il soit affichable à l'écran par exemple.
PathCompactPathExA | shlwapi.dll | Cette fonction permet de réduire la longueur en caractères d'un chemin de répertoire en remplaçant la partie trop longue par des abréviations pour qu'il soit affichable à l'écran par exemple.
PathCreateFromUrlA | shlwapi.dll | Cette fonction permet de convertir un URL sous sa forme du système d'exploitation de style DOS.
PathFindOnPathA | shlwapi.dll | Cette fonction permet d'effectuer la recherche d'un nom de fichier dans un répertoire.
PathGetDriveNumberA | shlwapi.dll | Cette fonction permet de demander le numéro de l'unité disque contenu dans un chemin de répertoire.
PathIsDirectoryEmptyA | shlwapi.dll | Cette fonction permet de demander si le répertoire spécifier est vide.
PathIsSystemFolder | shlwapi.dll | Cette fonction permet de demander si le répertoire spécifier à l'attribut de dossier système.
PathIsUNCA | shlwapi.dll | Cette fonction permet de demander si le répertoire est un répertoire UNC (Universal Naming Convention) valide.
PathIsUNCServerA | shlwapi.dll | Cette fonction permet de demander si le répertoire est un répertoire UNC (Universal Naming Convention) valide pour un serveur.
PathIsURLA | shlwapi.dll | Cette fonction permet de demander si le répertoire est un répertoire URL valide.
PathToRegion | gdi32.dll | Cette fonction permet de créer un chemin vers une région sélectionné dans le périphérique de contexte spécifié.
PathUndecorateA | shlwapi.dll | Cette fonction permet d'illiminer les séquences à numéro d'un nom fichier.
PathUnmakeSystemFolderA | shlwapi.dll | Cette fonction permet d'enlever l'attribut système d'un répertoire spécifié.
PdhAddCounterA | pdh.dll | Cette fonction permet d'ajouter le compteur spécifié à la requête pour les compteurs de performance.
PdhAddCounterW | pdh.dll | Cette fonction permet d'ajouter le compteur spécifié à la requête pour les compteurs de performance avec des paramètres de chaine de caractères étendue (Unicode).
PdhAddEnglishCounterA | pdh.dll | Cette fonction permet d'ajouter un compteur dans un langage humain neutre à la requête pour le compteur de performance.
PdhAddEnglishCounterW | pdh.dll | Cette fonction permet d'ajouter un compteur dans un langage humain neutre à la requête pour le compteur de performance avec des paramètres de chaine de caractères étendue (Unicode).
PdhAddRelogCounter | pdh.dll | Cette fonction permet d'effectuer la liaison d'un ou plusieurs fichiers binaires de journal de bord afin d'obtenir un ensemble dans le but de lire les données.
PdhAddV1Counter | pdh.dll | Cette fonction permet d'ajouter la première valeur pour le compteur de performance.
PdhAddV2Counter | pdh.dll | Cette fonction permet d'ajouter la seconde valeur pour le compteur de performance.
PdhBindInputDataSourceA | pdh.dll | Cette fonction permet d'effectuer la liaison d'un ou plusieurs fichiers binaires de journal de bord source afin d'obtenir un ensemble dans le but de lire les données.
PdhBindInputDataSourceW | pdh.dll | Cette fonction permet d'effectuer la liaison d'un ou plusieurs fichiers binaires de journal de bord source afin d'obtenir un ensemble dans le but de lire les données avec des paramètres de chaine de caractères étendue (Unicode).
PdhBrowseCountersA | pdh.dll | Cette fonction permet d'afficher une boite de dialogue de navigation de compteur que l'utilisateur peut utiliser afin de sélectionner un ou plusieurs compteurs quand il l'ajoute à la requête.
PdhBrowseCountersHA | pdh.dll | Cette fonction permet d'afficher une boite de dialogue de navigation de compteur que l'utilisateur peut utiliser afin de sélectionner un ou plusieurs compteurs quand il l'ajoute à la requête en utilisant un Handle.
PdhBrowseCountersHW | pdh.dll | Cette fonction permet d'afficher une boite de dialogue de navigation de compteur que l'utilisateur peut utiliser afin de sélectionner un ou plusieurs compteurs quand il l'ajoute à la requête en utilisant un Handle avec des paramètres de chaine de caractères étendue (Unicode).
PdhBrowseCountersW | pdh.dll | Cette fonction permet d'afficher une boite de dialogue de navigation de compteur que l'utilisateur peut utiliser afin de sélectionner un ou plusieurs compteurs quand il l'ajoute à la requête avec des paramètres de chaine de caractères étendue (Unicode).
PdhCalculateCounterFromRawValue | pdh.dll | Cette fonction permet d'effectuer le calcul des valeurs affichables de 2 valeurs brutes de compteur.
PdhCloseLog | pdh.dll | Cette fonction permet d'effectuer la fermeture d'un fichier de journal bord spécifié.
PdhCloseQuery | pdh.dll | Cette fonction permet de fermer toutes les compteurs contenant la requête spécifié, ferme tous les gestionnaires ayant la requête et libère toute la mémoire associé avec cette requête.
PdhCollectQueryData | pdh.dll | Cette fonction permet de collecter la donnée de valeur brute courante pour tous les compteurs dans la requête spécifié et met à jour le code d'état pour chaque compteur.
PdhCollectQueryDataEx | pdh.dll | Cette fonction permet d'utiliser un processus léger séparant la donnée de valeur brute courante pour tous les compteurs dans la requête spécifié.
PdhCollectQueryDataWithTime | pdh.dll | Cette fonction permet de collecter la données de valeur brute courante pour tous les compteurs dans la requête spécifié et met à jour le code d'état pour chaque compteur.
PdhComputeCounterStatistics | pdh.dll | Cette fonction permet d'effectuer les calculs statistiques pour un compteur dans un tableau de valeurs brute.
PdhConnectMachineA | pdh.dll | Cette fonction permet d'effectuer une connexion vers l'ordinateur spécifié pour les compteurs de performance.
PdhConnectMachineW | pdh.dll | Cette fonction permet d'effectuer une connexion vers l'ordinateur spécifié pour les compteurs de performance avec des paramètres de chaine de caractères étendue (Unicode).
PdhCreateSQLTablesA | pdh.dll | Cette fonction permet de créer des tables SQL pour le compteur performance.
PdhCreateSQLTablesW | pdh.dll | Cette fonction permet de créer des tables SQL pour le compteur performance avec des paramètres de chaine de caractères étendue (Unicode).
PdhEnumObjectItemsA | pdh.dll | Cette fonction permet de demander l'objet de compteur spécifié et les noms d'instance existant dans l'ordinateur spécifié ou le fichier de journal de bord spécifié.
PdhEnumObjectItemsW | pdh.dll | Cette fonction permet de demander l'objet de compteur spécifié et les noms d'instance existant dans l'ordinateur spécifié ou le fichier de journal de bord spécifié avec des paramètres de chaine de caractères étendue (Unicode).
PeekConsoleInputA | kernel32.dll | Cette fonction permet d'effectuer la lecture des données du tampon d'entrée de la console spécifié sans retirer le contenu lue du tampon.
PeekConsoleInputW | kernel32.dll | Cette fonction permet d'effectuer la lecture des données du tampon d'entrée de la console spécifié sans retirer le contenu lue du tampon avec des paramètres de chaine de caractères étendue (Unicode).
PeekMessageA | user32.dll | Cette fonction permet de poster un message dans une fenêtre de file d'attente de message et retourne sans attendre la correspondance à un processus de message.
PeekMessageW | user32.dll | Cette fonction permet de poster un message dans une fenêtre de file d'attente de message et retourne sans attendre la correspondance à un processus de message avec des paramètres de chaine de caractères étendue (Unicode).
PeekNamedPipe | kernel32.dll | Cette fonction permet de copier les données d'un tuyau nommé ou anonyme dans un tampon sans le retirer du tuyau.
Pie | gdi32.dll | Cette fonction permet de dessiner une pointe de tarte en effectuant un arc elliptique d'un centre et de deux points joins par des lignes.
PlayEnhMetaFile | gdi32.dll | Cette fonction permet d'afficher l'image entreposé dans le fichier méta de format étendue spécifié.
PlayEnhMetaFileRecord | gdi32.dll | Cette fonction permet de jouer un enregistrement de fichier méta étendue par exécution de fonction GDI (Graphics Device Interface) identifié par l'enregistrement.
PlayMetaFile | gdi32.dll | Cette fonction permet de jouer le contenu d'un fichier méta de format Windows spécifié dans un périphérique spécifié.
PlayMetaFileRecord | gdi32.dll | Cette fonction permet de jouer un enregistrement d'un fichier méta de format Windows par exécution du GDI contenu dans l'enregistrement.
PlaySoundA | winmm.dll | Cette fonction permet de faire jouer un fichier de son de format Wave (.WAV).
PlgBlt | gdi32.dll | Cette fonction permet d'effectuer un traitement de transfert de bloc de bits d'une donnée de bits de couleurs d'un rectangle spécifié dans une source de périphérique de contexte vers la destination de périphérique de contexte de parallélogramme spécifié.
PolyBezier | gdi32.dll | Cette fonction permet de dessiner une ou plusieurs courbes de Bézier.
PolyBezierTo | gdi32.dll | Cette fonction permet de dessiner une ou plusieurs courbes de Bézier spécifié.
PolyDraw | gdi32.dll | Cette fonction permet de dessiner un ensemble de ligne de segments et de courbes de Bézier.
PolyPolygon | gdi32.dll | Cette fonction permet de créer deux ou plusieurs polygone étant rempli par un mode de remplissage de polygone.
PolyTextOutA | gdi32.dll | Cette fonction permet de dessiner une chaine de caractères en utilisant la police de caractères et les couleurs textes courante dans le périphérique de contexte spécifié.
PolyTextOutW | gdi32.dll | Cette fonction permet de dessiner une chaine de caractères en utilisant la police de caractères et les couleurs textes courante dans le périphérique de contexte spécifié avec des paramètres de chaine de caractères étendues.
Polygon | gdi32.dll | Cette fonction permet de dessiner un polygone de deux ou plusieurs points (sommets) connectés par des lignes.
Polyline | gdi32.dll | Cette fonction permet de dessiner un ensemble de segments de lignes, connecté par les points spécifiés.
PolylineTo | gdi32.dll | Cette fonction permet de dessiner une ou plusieurs lignes droite.
PostMessageA | user32.dll | Cette fonction permet d'envoyer un message à un objet du système d'exploitation Windows.
PostMessageW | user32.dll | Cette fonction permet d'envoyer un message à un objet du système d'exploitation Windows avec des paramètres de chaine de caractères étendues (Unicode).
PostQueuedCompletionStatus | kernel32.dll | Cette fonction permet de poster une paquet d'achèvement d'entrée/sortie vers un port d'achèvement d'entrée/sortie.
PostQuitMessage | user32.dll | Cette fonction permet de poster un message à Windows afin d'indiquer que l'application reçoit une requête de terminaison d'exécution (quitte).
PostThreadMessageA | user32.dll | Cette fonction permet de poster un message dans la file d'attente du processus léger spécifié et retourne sans attendre que le processus léger traite le message.
PostThreadMessageW | user32.dll | Cette fonction permet de poster un message dans la file d'attente du processus léger spécifié et retourne sans attendre que le processus léger traite le message avec des paramètres de chaine de caractères étendues (Unicode).
PrepareTape | kernel32.dll | Cette fonction permet de préparer le ruban pour être accédé ou enlevé.
PrintDlgA | comdlg32.dll | Cette fonction permet d'afficher une boite de dialogue d'impression.
PrinterProperties | winspool.drv | Cette fonction permet d'afficher une boite de dialogue de propriété de l'imprimante.
PrivilegeCheck | advapi32.dll | Cette fonction permet de tester le contexte de sécurité représenté par un accès de jeton spécifié contenu dans un privilège spécifié.
PrivilegedServiceAuditAlarmA | advapi32.dll | Cette fonction permet de générer un message de journal de bord quand une opération de service de privilège système s'effectue.
PrivilegedServiceAuditAlarmW | advapi32.dll | Cette fonction permet de générer un message de journal de bord quand une opération de service de privilège système s'effectue avec des paramètres de chaine de caractères étendue (Unicode).
Process32First | kernel32.dll | Cette fonction permet de demander le premier processus provenant de la liste contenu dans une photo du système d'exploitation.
Process32Next | kernel32.dll | Cette fonction permet de demander le processus suivant provenant de la liste contenu dans une photo du système d'exploitation.
PtInRect | user32.dll | Cette fonction permet de déterminer si le point spécifié est lié avec le rectangle spécifié.
PtInRegion | gdi32.dll | Cette fonction permet de demander si un point spécifié est dans une région spécifié.
PtVisible | gdi32.dll | Cette fonction permet de demander si le point spécifié est dans la région clip d'un périphérique de contexte.
PulseEvent | kernel32.dll | Cette fonction permet de fournir une simple opération fixant l'état d'un objet d'événement spécifié et réinitialisant après qu'un délai d'attente de processus approprié s'est produit.
PurgeComm | kernel32.dll | Cette fonction permet de débarrasser de tous les caractères d'un tampon de sortie ou d'entrée d'une ressource de communication spécifié.
PwdChangePasswordA | mpr.dll | Cette fonction permet de modifier le mot de passe de l'utilisateur ou d'un fournisseur du système d'exploitation.
QueryDosDeviceA | kernel32.dll | Cette fonction permet à une application de demander des informations à propre d'un nom de périphérique DOS.
QueryDosDeviceW | kernel32.dll | Cette fonction permet à une application de demander des informations à propre d'un nom de périphérique DOS avec des paramètres de chaine de caractères étendue (Unicode).
QueryPerformanceCounter | kernel32.dll | Cette fonction permet de demander la valeur actuel du compteur de haute performance de la machine.
QueryPerformanceFrequency | kernel32.dll | Cette fonction permet de demander la valeur de la fréquence du compteur de haute performance de la machine.
QueueUserAPC | kernel32.dll | Cette fonction permet d'ajouter une objet de procédure d'appel désynchronisée (APC) en mode utilisateur dans une file d'attente APC d'un processus léger spécifié.
RaiseException | kernel32.dll | Cette fonction permet de déclencher une exception dans le processus léger appelant.
RasEnumEntriesA | rasapi32.dll | Cette fonction permet de demander la liste de tous les noms d'entrées contenu dans le carnet pour les connexions téléphoniques à distance.
ReadConsoleA | kernel32.dll | Cette fonction permet d'effectuer la lecture d'un caractère en provenance de la console.
ReadConsoleW | kernel32.dll | Cette fonction permet d'effectuer la lecture d'un caractère en provenance de la console avec des paramètres de chaine de caractères étendue (Unicode).
ReadConsoleInputA | kernel32.dll | Cette fonction permet d'effectuer la lecture de données du tampon d'entrée d'une console et retire les données du tampon.
ReadConsoleInputW | kernel32.dll | Cette fonction permet d'effectuer la lecture de données du tampon d'entrée d'une console et retire les données du tampon avec des paramètres de chaine de caractères étendue (Unicode).
ReadConsoleOutputA | kernel32.dll | Cette fonction permet d'effectuer la lecture de données de caractère et de couleur d'attribut dans un bloc rectangulaire d'une cellule de caractères d'un tampon de console d'écran et écrire le résultat de ses données dans un tampon spécifié.
ReadConsoleOutputW | kernel32.dll | Cette fonction permet d'effectuer la lecture de données de caractère et de couleur d'attribut dans un bloc rectangulaire d'une cellule de caractères d'un tampon de console d'écran et écrire le résultat de ses données dans un tampon spécifié avec des paramètres de chaine de caractères étendue (Unicode).
ReadConsoleOutputAttribute | kernel32.dll | Cette fonction permet de copier le nombre spécifié dans la couleur d'attribut d'arrière-plan ou de premier plan d'une cellule consécutive d'un tampon de console d'écran en débutant à la position spécifié.
ReadConsoleOutputCharacterA | kernel32.dll | Cette fonction permet de copier un nombre de caractères d'une cellule consécutif d'un tampon de console écran en débutant à la position spécifié.
ReadConsoleOutputCharacterW | kernel32.dll | Cette fonction permet de copier un nombre de caractères d'une cellule consécutif d'un tampon de console écran en débutant à la position spécifié avec des paramètres de chaine de caractères étendue (Unicode).
ReadDirectoryChangesW | kernel32.dll | Cette fonction permet de demander les informations descriptives de changement ayant eu lieu dans le répertoire spécifié.
ReadEventLogA | advapi32.dll | Cette fonction permet de lire un nombre d'entrées spécifié dans un journal de bord des événements.
ReadEventLogW | advapi32.dll | Cette fonction permet de lire un nombre d'entrées spécifié dans un journal de bord des événements avec des paramètres de chaine de caractères étendue (Unicode).
ReadFile | kernel32.dll | Cette fonction permet d'effectuer la lecture d'un fichier, en débutant à la position spécifié par le pointeur de fichier.
ReadFileEx | kernel32.dll | Cette fonction permet d'effectuer la lecture de données d'un fichier désynchronisé.
ReadProcessMemory | kernel32.dll | Cette fonction permet d'effectuer la lecture de mémoire dans un processus spécifié.
RealizePalette | gdi32.dll | Cette fonction permet de cartographier l'entrée de palette de la palette logique courante de la palette système.
Rectangle | gdi32.dll | Cette fonction permet d'afficher un rectangle.
RectInRegion | gdi32.dll | Cette fonction permet de demander si une partie du rectangle spécifié est dans une des limites d'une région spécifié.
RectVisible | gdi32.dll | Cette fonction permet de demander n'importe quel partie d'un rectangle spécifié dans une région de clip d'un périphérique de contexte spécifié.
RedrawWindow | user32.dll | Cette fonction permet de mettre à jour le rectangle ou la région spécifié de la région de la fenêtre client.
RegCloseKey | advapi32.dll | Cette fonction permet de fermer le Handle de la clef de registre spécifié.
RegConnectRegistryA | advapi32.dll | Cette fonction permet d'établir une connexion avec un registre de Handle prédéfinie ou autre ordinateur.
RegConnectRegistryW | advapi32.dll | Cette fonction permet d'établir une connexion avec un registre de Handle prédéfinie ou autre ordinateur avec des paramètres de chaine de caractères étendue (Unicode).
RegCreateKeyA | advapi32.dll | Cette fonction permet de créer la clef de registre spécifié. Si la clef existe dans les registres, la fonction l'ouvre.
RegCreateKeyW | advapi32.dll | Cette fonction permet de créer la clef de registre spécifié avec des paramètres de chaine de caractères étendue (Unicode). Si la clef existe dans les registres, la fonction l'ouvre.
RegCreateKeyExA | advapi32.dll | Cette fonction permet de créer une clef de registre spécifié avec des systèmes de la famille NT. Si la clef existe dans les registres, la fonction l'ouvre.
RegCreateKeyExW | advapi32.dll | Cette fonction permet de créer une clef de registre spécifié avec des systèmes de la famille NT avec des paramètres de chaine de caractères étendue (Unicode). Si la clef existe dans les registres, la fonction l'ouvre.
RegDeleteKeyA | advapi32.dll | Cette fonction permet de supprimer la clef de registre spécifié.
RegDeleteKeyW | advapi32.dll | Cette fonction permet de supprimer la clef de registre spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegDeleteValueA | advapi32.dll | Cette fonction permet de supprimer la valeur du nom spécifié dans la clef de la base de registre.
RegDeleteValueW | advapi32.dll | Cette fonction permet de supprimer la valeur du nom spécifié dans la clef de la base de registre avec des paramètres de chaine de caractères étendue (Unicode).
RegEnumKeyA | advapi32.dll | Cette fonction permet d'énuméré les sous-clefs d'une clef de registre ouverte spécifié.
RegEnumKeyW | advapi32.dll | Cette fonction permet d'énuméré les sous-clefs d'une clef de registre ouverte spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegEnumKeyExA | advapi32.dll | Cette fonction permet d'énuméré les sous-clefs d'une clef de registre ouverte spécifié avec des systèmes de la famille NT.
RegEnumKeyExW | advapi32.dll | Cette fonction permet d'énuméré les sous-clefs d'une clef de registre ouverte spécifié avec des systèmes de la famille NT avec des paramètres de chaine de caractères étendue (Unicode).
RegEnumValueA | advapi32.dll | Cette fonction permet d'énuméré les valeurs pour la clef de registre ouverte spécifié.
RegEnumValueW | advapi32.dll | Cette fonction permet d'énuméré les valeurs pour la clef de registre ouverte spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegFlushKey | advapi32.dll | Cette fonction permet d'écrire tous les attributs de la clef de la base de registre ouverte spécifié.
RegGetKeySecurity | advapi32.dll | Cette fonction permet de copier le descripteur de sécurité de la clef de registre ouverte spécifié.
RegisterClassA | user32.dll | Cette fonction permet d'effectuer l'enregistrement d'une classe de fenêtre subséquente aux appels des fonctions CreateWindow ou CreateWindowEx.
RegisterClassExA | user32.dll | Cette fonction permet d'effectuer l'enregistrement d'une classe de fenêtre subséquente aux appels des fonctions CreateWindow ou CreateWindowEx avec des options supplémentaires.
RegisterClassExW | user32.dll | Cette fonction permet d'effectuer l'enregistrement d'une classe de fenêtre subséquente aux appels des fonctions CreateWindow ou CreateWindowEx avec des options supplémentaires et des paramètres de chaine de caractères étendues (Unicode).
RegisterClassW | user32.dll | Cette fonction permet d'effectuer l'enregistrement d'une classe de fenêtre subséquente aux appels des fonctions CreateWindow ou CreateWindowEx avec des paramètres de chaine de caractères étendues (Unicode).
RegisterClipboardFormatA | user32.dll | Cette fonction permet d'enregistrer un nouveau format de presse-papier.
RegisterClipboardFormatW | user32.dll | Cette fonction permet d'enregistrer un nouveau format de presse-papier avec des paramètres de chaine de caractères étendues (Unicode).
RegisterDeviceNotificationA | user32.dll | Cette fonction permet d'enregistrer un périphérique ou un type de périphérique pour que la fenêtre reçoivent des avertissements de changements.
RegisterHotKey | user32.dll | Cette fonction permet de définir l'état des touches de combinaison (Alt, Ctrl, Shift) pour le processus léger courant.
RegisterServiceProcess | kernel32.dll | Cette fonction permet de cacher un programme de façon à ce qu'il ne soit pas afficher lorsqu'on appuis sur Ctrl+Alt+Delete pour voir la liste des programmes dans le Gestionnaire de tâche.
RegisterWindowMessageA | user32.dll | Cette fonction permet de définir une nouvelle fenêtre de message garantissant qu'elle est unique au système.
RegisterWindowMessageW | user32.dll | Cette fonction permet de définir une nouvelle fenêtre de message garantissant qu'elle est unique au système avec des paramètres de chaine de caractères étendues (Unicode).
RegLoadKeyA | advapi32.dll | Cette fonction permet de créer une sous-clef dans «HKEY_USER» ou «HKEY_LOCAL_MACHINE» et d'entreposer les informations d'enregistrement dans le fichier spécifié d'une sous-clef.
RegLoadKeyW | advapi32.dll | Cette fonction permet de créer une sous-clef dans «HKEY_USER» ou «HKEY_LOCAL_MACHINE» et d'entreposer les informations d'enregistrement dans le fichier spécifié d'une sous-clef avec des paramètres de chaine de caractères étendue (Unicode).
RegNotifyChangeKeyValue | advapi32.dll | Cette fonction permet d'indiquer quand une clef de registre, ou élément rattaché à celle-ci, a changé.
RegOpenKeyA | advapi32.dll | Cette fonction permet d'ouvrir une clef spécifié dans une base de registre.
RegOpenKeyW | advapi32.dll | Cette fonction permet d'ouvrir une clef spécifié dans une base de registre avec des paramètres de chaine de caractères étendue (Unicode).
RegOpenKeyExA | advapi32.dll | Cette fonction permet d'ouvrir une clef spécifié dans une base de registre.
RegOpenKeyExW | advapi32.dll | Cette fonction permet d'ouvrir une clef spécifié dans une base de registre avec des paramètres de chaine de caractères étendue (Unicode).
RegQueryInfoKeyA | advapi32.dll | Cette fonction permet de demander les informations d'une clef de registre spécifié.
RegQueryInfoKeyW | advapi32.dll | Cette fonction permet de demander les informations d'une clef de registre spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegQueryMultipleValuesA | advapi32.dll | Cette fonction permet de demander le type et la donnée pour une liste de noms de valeur associé avec une clef de registre ouverte.
RegQueryMultipleValuesW | advapi32.dll | Cette fonction permet de demander le type et la donnée pour une liste de noms de valeur associé avec une clef de registre ouverte avec des paramètres de chaine de caractères étendue (Unicode).
RegQueryValueA | advapi32.dll | Cette fonction permet de demander la valeur associé avec une valeur non-nommé pour la clef du registre spécifié.
RegQueryValueW | advapi32.dll | Cette fonction permet de demander la valeur associé avec une valeur non-nommé pour la clef du registre spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegQueryValueExA | advapi32.dll | Cette fonction permet de demander la valeur associé avec une valeur non-nommé pour la clef du registre spécifié avec des systèmes de la famille NT.
RegQueryValueExW | advapi32.dll | Cette fonction permet de demander la valeur associé avec une valeur non-nommé pour la clef du registre spécifié avec des systèmes de la famille NT avec des paramètres de chaine de caractères étendue (Unicode).
RegReplaceKeyA | advapi32.dll | Cette fonction permet de remplacer un fichier de copie de sauvegarde d'un clef et tous les sous-clef d'une autre fichier.
RegReplaceKeyW | advapi32.dll | Cette fonction permet de remplacer un fichier de copie de sauvegarde d'un clef et tous les sous-clef d'une autre fichier avec des paramètres de chaine de caractères étendue (Unicode).
RegRestoreKeyA | advapi32.dll | Cette fonction permet de lire des informations registre dans un fichier spécifié et de copier la clef spécifié.
RegRestoreKeyW | advapi32.dll | Cette fonction permet de lire des informations registre dans un fichier spécifié et de copier la clef spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegSaveKeyA | advapi32.dll | Cette fonction permet de sauvegarder tous les clefs, sous-clefs et les valeurs spécifié dans un nouveau fichier.
RegSaveKeyW | advapi32.dll | Cette fonction permet de sauvegarder tous les clefs, sous-clefs et les valeurs spécifié dans un nouveau fichier avec des paramètres de chaine de caractères étendue (Unicode).
RegSetKeySecurity | advapi32.dll | Cette fonction permet fixer la sécurité d'une clef de registre ouvert.
RegSetValueA | advapi32.dll | Cette fonction permet d'associer une valeur avec une clef spécifié.
RegSetValueW | advapi32.dll | Cette fonction permet d'associer une valeur avec une clef spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegSetValueExA | advapi32.dll | Cette fonction permet d'entreposer les données d'un champs de valeur d'une clef de registre ouvert.
RegSetValueExW | advapi32.dll | Cette fonction permet d'entreposer les données d'un champs de valeur d'une clef de registre ouvert avec des paramètres de chaine de caractères étendue (Unicode).
RegUnLoadKeyA | advapi32.dll | Cette fonction permet de décharger la clef et sous-clefs de registre spécifié
RegUnLoadKeyW | advapi32.dll | Cette fonction permet de décharger la clef et sous-clefs de registre spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegisterEventSourceA | advapi32.dll | Cette fonction permet de retourner l'identificateur Handle utilisé avec la fonction d'événement «ReportEvent» du journal de bord.
RegisterEventSourceW | advapi32.dll | Cette fonction permet de retourner l'identificateur Handle utilisé avec la fonction d'événement «ReportEvent» du journal de bord avec des paramètres de chaine de caractères étendue (Unicode).
ReleaseCapture | user32.dll | Cette fonction permet de relâcher la capture de souris et de restituer l'entrée de traitement normal.
ReleaseDC | user32.dll | Cette fonction permet de relâcher un périphérique de contexte, de libérer son utilisation pour les autres applications.
ReleaseMutex | kernel32.dll | Cette fonction permet de relâcher la propriété de l'objet Mutex spécifié.
ReleaseSemaphore | kernel32.dll | Cette fonction permet d'augmenter le compteur de l'objet de sémaphore spécifié par une quantité spécifié.
RemoveDirectoryA | kernel32.dll | Cette fonction permet de supprimer un répertoire.
RemoveDirectoryW | kernel32.dll | Cette fonction permet de supprimer un répertoire avec les paramètres de chaine de caractères étendues (Unicode).
RemoveDirectoryTransactedA | kernel32.dll | Cette fonction permet de supprimer un répertoire d'une opération de transaction.
RemoveDirectoryTransactedW | kernel32.dll | Cette fonction permet de supprimer un répertoire d'une opération de transaction avec des paramètres de chaine de caractères étendue (Unicode).
RemoveFontResourceA | gdi32.dll | Cette fonction permet d'enlever un ajout de ressource de police de caractères d'un fichier spécifié ou d'une table de police de caractères de Windows.
RemoveFontResourceW | gdi32.dll | Cette fonction permet d'enlever un ajout de ressource de police de caractères d'un fichier spécifié ou d'une table de police de caractères de Windows avec des paramètres de chaine de caractères étendues (Unicode).
RemoveMenu | user32.dll | Cette fonction permet de supprimer un item de menu déroulant.
RemovePropA | user32.dll | Cette fonction permet d'enlever une entrée de la liste de propriété de la fenêtre spécifié.
RemovePropW | user32.dll | Cette fonction permet d'enlever une entrée de la liste de propriété de la fenêtre spécifié avec des paramètres de chaine de caractères étendues (Unicode).
ReplyMessage | user32.dll | Cette fonction permet de répondre à un message envoyé par la fonction SendMessage sans rendre le contrôle à la fonction ayant appelé SendMessage.
ReportEventA | advapi32.dll | Cette fonction permet d'écrire une entrée à la fin du journal de bord des événements spécifié.
ReportEventW | advapi32.dll | Cette fonction permet d'écrire une entrée à la fin du journal de bord des événements spécifié avec des paramètres de chaine de caractères étendue (Unicode).
ResetDCA | gdi32.dll | Cette fonction permet de mettre à jour le périphérique de contexte, en se basant sur les informations de la structure spécifié.
ResetDCW | gdi32.dll | Cette fonction permet de mettre à jour le périphérique de contexte, en se basant sur les informations de la structure spécifié avec des paramètres de chaine de caractères étendues (Unicode).
ResetEvent | kernel32.dll | Cette fonction permet de fixer l'état de l'objet d'événement spécifié dans un sans signal.
ResizePalette | gdi32.dll | Cette fonction permet de changer la taille de la palette logique spécifié.
RestoreDC | gdi32.dll | Cette fonction permet de restaurer un périphérique de contexte à l'état précédent.
RestoreMyDocsFolder | mydocs.dll | Cette fonction permet de recréer l'icône de «Mes Documents» sur le bureau du système d'exploitation Windows.
ResumeThread | kernel32.dll | Cette fonction permet de décrémenter le compteur de processus léger suspendu.
ReuseDDElParam | user32.dll | Cette fonction permet d'allouer une application vers la réutilisation de paramètre DDE compacté spécifié et en réallouant un nouveau paquet spécifié.
RevertToSelf | advapi32.dll | Cette fonction permet de terminé la personnification d'une application client.
RtlFillMemory | kernel32.dll | Cette fonction permet de remplir une zone de mémoire avec une valeur spécifié.
RtlMoveMemory | kernel32.dll | Cette fonction permet de copier un bloc de mémoire vers un autre emplacement mémoire.
RoundRect | gdi32.dll | Cette fonction permet de dessiner un rectangle avec les coins arrondies, en utilisant le crayon courant.
SaveDC | gdi32.dll | Cette fonction permet de sauvegarder l'état courant du périphérique de contexte courant en copiant les informations d'état (clip de région, sélection d'objets, et mode de cartographie) dans la pile de contexte.
ScaleViewportExtEx | gdi32.dll | Cette fonction permet de modifier les extensions du point de vue relative avec des valeurs courante.
ScaleWindowExtEx | gdi32.dll | Cette fonction permet de modifier les extensions de la fenêtre de périphérique de contexte en utilisant le ratio formé par le multiplicateur et le diviseur spécifié.
ScreenToClient | user32.dll | Cette fonction permet de convertir les coordonnées d'écrans d'un point spécifié de l'écran à des coordonnées clients.
ScrollConsoleScreenBufferA | kernel32.dll | Cette fonction permet de déplacer un bloc de données dans un tampon d'écran.
ScrollConsoleScreenBufferW | kernel32.dll | Cette fonction permet de déplacer un bloc de données dans un tampon d'écran avec des paramètres de chaine de caractères étendues (Unicode).
ScrollDC | user32.dll | Cette fonction permet de faire défiler un rectangle d'un nombre de bits horizontal et vertical.
ScrollWindow | user32.dll | Cette fonction permet d'effectuer un défilement du contenu d'une région de fenêtre client.
ScrollWindowEx | user32.dll | Cette fonction permet d'effectuer un défilement du contenu d'une région de fenêtre client avec des options supplémentaires.
SearchPathA | kernel32.dll | Cette fonction permet d'effectuer une recherche dans le fichier spécifié.
SearchPathW | kernel32.dll | Cette fonction permet d'effectuer une recherche dans le fichier spécifié avec des paramètres de chaine de caractères étendues (Unicode).
SearchTreeForFile | imagehlp.dll | Cette fonction permet d'effectuer la recherche d'un fichier dans un arbre de répertoire.
SelectClipPath | gdi32.dll | Cette fonction permet de sélectionner le chemin courant dans la région de capture d'un périphérique de contexte en combinant la nouvelle région avec n'importe quel région de capture existante par utilisation de mode spécifié.
SelectClipRgn | gdi32.dll | Cette fonction permet de sélectionner une région spécifié et la capture de région courant pour un périphérique de contexte courant.
SelectObject | gdi32.dll | Cette fonction permet de sélectionner un objet dans le périphérique de contexte spécifié.
SelectPalette | gdi32.dll | Cette fonction permet de sélectionner la palette logique dans le périphérique de contexte donnée.
SendDlgItemMessageA | user32.dll | Cette fonction permet d'envoyer un message à un contrôle de boite de dialogue.
SendDlgItemMessageW | user32.dll | Cette fonction permet d'envoyer un message à un contrôle de boite de dialogue avec des paramètres de chaine de caractères étendues (Unidcode).
SendMessageA | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message à une fenêtre où à un objet d'une fenêtre.
SendMessageCallbackA | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message spécifié vers une ou des fenêtres.
SendMessageCallbackW | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message spécifié vers une ou des fenêtres avec des paramètres de chaine de caractères étendues (Unicode).
SendMessageTimeoutA | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message avec un dépassement de délai spécifié vers une ou des fenêtres.
SendMessageTimeoutW | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message avec un dépassement de délai spécifié vers une ou des fenêtres avec des paramètres de chaine de caractères étendues (Unicode).
SendMessageW | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message à une fenêtre où à un objet d'une fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
SendNotifyMessageA | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message d'avertissement spécifié vers une fenêtre.
SendNotifyMessageW | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message d'avertissement spécifié vers une fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
SetAbortProc | gdi32.dll | Cette fonction permet de fixer la procédure d'application définit par le programmeur pour alloué un travail d'impression afin d'être annuler durant sa tamporisation dans le tampon d'impression.
SetActiveWindow | user32.dll | Cette fonction permet de rendre le plus haut niveau d'une fenêtre spécifié en une fenêtre active.
SetAclInformation | advapi32.dll | Cette fonction permet de fixer les informations à propos de la liste de contrôle d'accès (ACL).
SetArcDirection | gdi32.dll | Cette fonction permet de fixer la direction du dessinage pour être utilisé dans les fonctions d'arc et de rectangle.
SetBitmapBits | gdi32.dll | Cette fonction permet de fixer l'ensemble de bits d'un Bitmap spécifié avec les valeurs de bits spécifié.
SetBitmapDimensionEx | gdi32.dll | Cette fonction permet de fixer la taille souhaité d'un Bitmap, dans une unité de mesure de 0,01 millimètre.
SetBkColor | gdi32.dll | Cette fonction permet de fixer la couleur de l'arrière-plan de la couleur spécifié.
SetBkMode | gdi32.dll | Cette fonction permet de fixer le mode d'arrière-plan spécifié.
SetBoundsRect | gdi32.dll | Cette fonction permet de contrôler l'accumulation d'informations de limite rectangulaire d'un périphérique de contexte.
SetBrushOrgEx | gdi32.dll | Cette fonction permet de fixer l'origine de la brosse GDI étant associé avec la prochaine brosse d'une application sélectionné dans le périphérique de contexte spécifié.
SetCapture | user32.dll | Cette fonction permet de fixer la capture de la souris dans la fenêtre spécifié.
SetCaretBlinkTime | user32.dll | Cette fonction permet de fixer le taux de clignotement du caret.
SetCaretPos | user32.dll | Cette fonction permet de fixer la position d'un caret.
SetClassLongA | user32.dll | Cette fonction permet de fixer des valeurs d'informations en 32 bits sur une classe.
SetClassLongW | user32.dll | Cette fonction permet de fixer des valeurs d'informations en 32 bits sur une classe avec des paramètres de chaine de caractères étendues (Unicode).
SetClassWord | user32.dll | Cette fonction permet de fixer la valeur d'un mot dans le déplacement spécifié de la classe de mémoire supplémentaire de la classe de fenêtre à laquelle la fenêtre appartient.
SetClipboardData | user32.dll | Cette fonction permet de fixer des données dans le presse-papier.
SetClipboardViewer | user32.dll | Cette fonction permet d'ajouter une fenêtre donnée à la chaine de fenêtre ayant un avertissement si le contenu du presse-papier a changé.
SetColorAdjustment | gdi32.dll | Cette fonction permet de fixer les valeurs de couleurs d'ajustements pour un périphérique de contexte utilisant les valeurs spécifiés.
SetColorSpace | gdi32.dll | Cette fonction permet de fixer l'espace de couleur du périphérique de contexte spécifié en définissant la fin des points d'un espace RVB (RGB) logique dans l'espace CIE XYZ.
SetCommBreak | kernel32.dll | Cette fonction permet de suspendre la transmission de caractères pour un périphérique de communications spécifié et place la ligne de transmission dans un état d'arrêt jusqu'à ce que la fonction ClearCommBreak soit appelée.
SetCommConfig | kernel32.dll | Cette fonction permet de fixer la configuration courante dans un périphérique de communication.
SetCommMask | kernel32.dll | Cette fonction permet de spécifier un ensemble d'événement être surveillé par un périphérique de communication.
SetCommState | kernel32.dll | Cette fonction permet de configurer un périphérique de communication en accord avec bloc de contrôle de périphérique (DCB) spécifié.
SetCommTimeouts | kernel32.dll | Cette fonction permet de fixer les paramètres de dépassement de délai pour tous les opérations de lecture ou d'écriture dans un périphérique de communication spécifié.
SetComputerNameA | kernel32.dll | Cette fonction permet de fixer le nom de l'ordinateur pour le système d'exploitation Windows.
SetComputerNameW | kernel32.dll | Cette fonction permet de fixer le nom de l'ordinateur pour le système d'exploitation Windows avec des paramètres de chaine de caractères étendues (Unicode).
SetConsoleActiveScreenBuffer | kernel32.dll | Cette fonction permet de fixer le tampon d'écran spécifié devant être actuellement affiché dans le tampon de la console d'écran.
SetConsoleCP | kernel32.dll | Cette fonction permet de fixer l'entrée de code de page utilisé par la console associé avec le processus appelant.
SetConsoleCtrlHandler | kernel32.dll | Cette fonction permet d'ajouter un d'enlever une définition d'application de la fonction HandlerRoutine de la liste des fonctions de gestionnaire pour un processus appelant.
SetConsoleCursorInfo | kernel32.dll | Cette fonction permet de fixer la taille et la visibilité du curseur dans le tampon de la console d'écran spécifié.
SetConsoleCursorPosition | kernel32.dll | Cette fonction permet de fixer la position du curseur pour l'affichage sur la console de texte.
SetConsoleMode | kernel32.dll | Cette fonction permet de fixer le mode d'entrée du tampon d'entrée de la console ou du mode de sortie d'un tampon de console d'écran.
SetConsoleOutputCP | kernel32.dll | Cette fonction permet de fixer le code de page de sortie utilisé par la console associé avec le processus appelant.
SetConsoleScreenBufferSize | kernel32.dll | Cette fonction permet de changer la taille d'un tampon de console d'écran spécifié.
SetConsoleTextAttribute | kernel32.dll | Cette fonction permet de fixer les attributs (couleur de fond et d'écriture) pour l'affichage sur la console de texte. C'est l'équivalent d'un octet d'attribut dans un écran de texte matériel.
SetConsoleTitleA | kernel32.dll | Cette fonction permet de fixer le titre de la barre de titre de la fenêtre d'application.
SetConsoleTitleW | kernel32.dll | Cette fonction permet de fixer le titre de la barre de titre de la fenêtre d'application avec des paramètres de chaine de caractères étendues (Unicode).
SetConsoleWindowInfo | kernel32.dll | Cette fonction permet de fixer la taille courante et la position de la fenêtre du tampon de la console d'écran.
SetCurrentDirectoryA | kernel32.dll | Cette fonction permet de fixer le répertoire courant du processus courant.
SetCurrentDirectoryW | kernel32.dll | Cette fonction permet de fixer le répertoire courant du processus courant avec des paramètres de chaine de caractères étendues (Unicode).
SetCursor | user32.dll | Cette fonction permet de fixer l'image du pointeur de la souris.
SetCursorPos | user32.dll | Cette fonction permet de fixer les coordonnées du pointeur de la souris.
SetDebugErrorLevel | user32.dll | Cette fonction permet de fxer le niveau d'erreur minimum dans chaque fenêtre étant généré par les événements de déboguage et passe celui-ci au débogueur.
SetDefaultCommConfigA | kernel32.dll | Cette fonction permet de fixer la configuration par défaut pour un périphérique de communication.
SetDefaultCommConfigW | kernel32.dll | Cette fonction permet de fixer la configuration par défaut pour un périphérique de communication avec des paramètres de chaine de caractères étendues (Unicode).
SetDeviceGammaRamp | gdi32.dll | Cette fonction permet de fixer la rampe gamma d'un panneau d'affichage de couleur directe.
SetDIBColorTable | gdi32.dll | Cette fonction permet de fixer les valeurs de couleurs RVB (rouge, vert, bleu) dans une rangée de table de couleurs d'un périphérique de Bitmap indépendant (DIB) étant actuellement sélectionné dans un périphérique de contexte spécifié.
SetDIBits | gdi32.dll | Cette fonction permet de fixer le pixel dans un Bitmap compatible (DDB) en utilisant les données de couleur trouvé dans le DIB spécifié.
SetDIBitsToDevice | gdi32.dll | Cette fonction permet de fixer les bits d'un périphérique de Bitmap indépendant (DIB) directement dans un périphérique de surface.
SetDlgItemInt | user32.dll | Cette fonction permet de fixer le texte d'un contrôle donnée dans une boite de dialogue vers la chaine de caractères de représentation spécifiant la valeur entière.
SetDlgItemTextA | user32.dll | Cette fonction permet de fixer le titre ou le texte d'un contrôle d'une boite de dialogue.
SetDlgItemTextW | user32.dll | Cette fonction permet de fixer le titre ou le texte d'un contrôle d'une boite de dialogue avec des paramètres de chaine de caractères étendues (Unicode).
SetDoubleClickTime | user32.dll | Cette fonction permet de fixer le temps maximal autorisé pour que des clics sur les boutons de la souris soit considéré comme un double-clic.
SetEndOfFile | kernel32.dll | Cette fonction permet de fixer la position du pointeur de fichier à la fin du fichier utilisant un Handle.
SetEnhMetaFileBits | gdi32.dll | Cette fonction permet de créer un fichier méta de format étendue, basé en mémoire, de données fournit.
SetEnvironmentVariableA | kernel32.dll | Cette fonction permet de fixer la valeur d'une variable d'environnement du système d'exploitation.
SetEnvironmentVariableW | kernel32.dll | Cette fonction permet de fixer la valeur d'une variable d'environnement du système d'exploitation avec des paramètres de chaine de caractères étendues (Unicode).
SetErrorMode | kernel32.dll | Cette fonction permet de contrôler comment le système d'exploitation gère les types sévères dans les erreurs sérieuses.
SetEvent | kernel32.dll | Cette fonction permet de fixer l'état de l'objet d'événement spécifié par un signal.
SetFileApisToANSI | kernel32.dll | Cette fonction permet de faire provoquer, à une série de fonctions de fichiers Win32, l'utilisation d'un ensemble de code de page de caractères ANSI.
SetFileApisToOEM | kernel32.dll | Cette fonction permet de faire provoquer, à une série de fonctions de fichiers Win32, l'utilisation d'un ensemble de code de page de caractères OEM.
SetFileAttributesA | kernel32.dll | Cette fonction permet de fixer les attributs d'un fichier ou d'un répertoire.
SetFileAttributesW | kernel32.dll | Cette fonction permet de fixer les attributs d'un fichier ou d'un répertoire avec des paramètres de chaine de caractères étendues (Unicode).
SetFilePointer | kernel32.dll | Cette fonction permet de déplacer un pointeur de ficher dans un fichier ouvert.
SetFileSecurityA | advapi32.dll | Cette fonction permet de fixer la sécurité d'un objet de fichier ou d'un répertoire.
SetFileSecurityW | advapi32.dll | Cette fonction permet de fixer la sécurité d'un objet de fichier ou d'un répertoire avec des paramètres de chaine de caractères étendue (Unicode).
SetFileTime | kernel32.dll | Cette fonction permet de fixer la date et l'heure de la création, du dernière accès et de la dernière modification d'un fichier.
SetFocus | user32.dll | Cette fonction permet de fixer le focus d'entrée de la fenêtre spécifié.
SetForegroundWindow | user32.dll | Cette fonction permet de mettre le processus léger étant créer pour la fenêtre spécifié dans une fenêtre active ou de premier plan.
SetGraphicsMode | gdi32.dll | Cette fonction permet de fixer le mode graphique pour le périphérique de contexte spécifié.
SetHandleCount | kernel32.dll | Cette fonction permet de changer le nombre de fichier Handle disponible pour un processus.
SetHandleInformation | kernel32.dll | Cette fonction permet de fixer certaines propriétés d'un objet Handle.
SetICMMode | gdi32.dll | Cette fonction permet d'activer ou désactiver la correspondance de couleur d'image pour le périphérique de contexte spécifié.
SetICMProfileA | gdi32.dll | Cette fonction permet de fixer la couleur de profile pour le périphérique de contexte spécifié.
SetICMProfileW | gdi32.dll | Cette fonction permet de fixer la couleur de profile pour le périphérique de contexte spécifié avec des paramètres de chaine de caractères étendues (Unicode).
SetJobA | winspool.drv | Cette fonction permet de fixer l'état du traitement d'un travail contenu dans le tampon d'impressions de l'imprimante.
SetKeyboardState | user32.dll | Cette fonction permet de fixer l'état des 256 touches virtuel de clavier.
SetKernelObjectSecurity | advapi32.dll | Cette fonction permet de fixer la sécurité de l'objet du noyau du système d'exploitation.
SetLastError | kernel32.dll | Cette fonction permet de fixer le code de la dernière erreur d'un appel de processus léger.
SetLastErrorEx | user32.dll | Cette fonction permet de fixer le code de la dernière erreur avec un type d'erreur.
SetLocalTime | kernel32.dll | Cette fonction permet de fixer la date et l'heure locale courante.
SetLocaleInfoA | kernel32.dll | Cette fonction permet de fixer un item d'information local.
SetLocaleInfoW | kernel32.dll | Cette fonction permet de fixer un item d'information local avec des paramètres de chaine de caractères étendues (Unicode).
SetMailslotInfo | kernel32.dll | Cette fonction permet de fixer la valeur de dépassement de délai utilisable pour le mailslot spécifié d'une opération de lecture.
SetMapMode | gdi32.dll | Cette fonction permet de fixer le mode de cartographie d'un périphérique de contexte spécifié.
SetMapperFlags | gdi32.dll | Cette fonction permet de changer la méthode utilisé pour la cartographie d'une police de caractères lors d'une conversion de police de caractères logique vers une police de caractères physique.
SetMenu | user32.dll | Cette fonction permet d'attribué un menu à une fenêtre.
SetMenuContextHelpId | user32.dll | Cette fonction permet d'associer un identificateur de contexte d'aide avec un menu.
SetMenuDefaultItem | user32.dll | Cette fonction permet de fixer l'item par défaut d'un menu déroulant.
SetMenuItemBitmaps | user32.dll | Cette fonction permet d'associer le Bitmap donnée avec un item de menu.
SetMenuItemInfoA | user32.dll | Cette fonction permet de modifier les informations conçernant un item de menu.
SetMenuItemInfoW | user32.dll | Cette fonction permet de modifier les informations conçernant un item de menu avec des paramètres de chaine de caractères étendues (Unicode).
SetMessageQueue | user32.dll | Cette fonction permet de créer un nouveau message dans la file d'attente.
SetMetaFileBitsEx | gdi32.dll | Cette fonction permet de créer un fichier méta de format Windows, basé en mémoire, pour les données fournit.
SetMetaRgn | gdi32.dll | Cette fonction permet d'effectuer l'intersection de la région de capture courante pour le périphérique de contexte spécifié avec la région méta courante et sauvegarde la combinaison des régions dans une nouvelle région méta.
SetMiterLimit | gdi32.dll | Cette fonction permet de fixer la limite pour la longueur d'une jointure de mitre pour le périphérique de contexte spécifié.
SetNamedPipeHandleState | kernel32.dll | Cette fonction permet de fixer le mode de lecture et le mode de bloc d'un tuyau nommé spécifié.
SetPaletteEntries | gdi32.dll | Cette fonction permet de fixer les valeurs de couleurs RVB (ou RGB en anglais) et les drapeaux d'un rang des entrées dans une palette logique spécifié.
SetParent | user32.dll | Cette fonction permet de transférer un objet d'une fenêtre vers une autre.
SetPixel | gdi32.dll | Cette fonction permet de fixer la couleur sous format RVB (RGB) d'un pixel affiché à l'écran.
SetPixelFormat | gdi32.dll | Cette fonction permet de fixer le format de pixel du périphérique de contexte spécifié au format spécifié par l'index spécifié.
SetPixelV | gdi32.dll | Cette fonction permet de fixer le pixel aux coordonnées de fermeture approximatifs spécifiées avec la couleur spécifié.
SetPolyFillMode | gdi32.dll | Cette fonction permet de fixer le mode de remplissage d'un polygone spécifié.
SetPrinterA | winspool.drv | Cette fonction permet d'envoyer une commande à l'imprimante.
SetPriorityClass | kernel32.dll | Cette fonction permet de fixer la priorité d'une classe pour le processus spécifié.
SetPrivateObjectSecurity | advapi32.dll | Cette fonction permet de modifier un descripteur de sécurité d'objet privé.
SetProcessAffinityMask | kernel32.dll | Cette fonction permet de fixer un masque d'affinité de processeur pour le processus léger d'un processus spécifié.
SetProcessPriorityBoost | kernel32.dll | Cette fonction permet d'activer ou désactiver les habilitées du système pour stimuler temporairement la priorité d'un processus léger dans un processus spécifié.
SetProcessShutdownParameters | kernel32.dll | Cette fonction permet de fixer les paramètres de fermeture du système pour le processus appelant courant.
SetProcessWindowStation | user32.dll | Cette fonction permet de fixer une fenêtre de station vers un processus appellant.
SetProcessWorkingSetSize | kernel32.dll | Cette fonction permet de fixer la taille du travail minimum et maximum d'un processus spécifié.
SetPropA | user32.dll | Cette fonction permet d'ajouter une nouvelle entrée ou changer une entrée existante dans la liste de propriété d'une fenêtre spécifié.
SetPropW | user32.dll | Cette fonction permet d'ajouter une nouvelle entrée ou changer une entrée existante dans la liste de propriété d'une fenêtre spécifié avec des paramètres de chaine de caractères étendues (Unicode).
SetRect | user32.dll | Cette fonction permet de fixer la taille et la position d'un rectangle.
SetRectEmpty | user32.dll | Cette fonction permet de créer un rectangle vide (tous les coordonnées sont fixées à 0).
SetRectRgn | gdi32.dll | Cette fonction permet de changer la région spécifié dans un région rectangulaire avec les coordonnées spécifiés.
SetROP2 | gdi32.dll | Cette fonction permet de fixer le mode de dessinage courant.
SetScrollInfo | user32.dll | Cette fonction permet de fixer les paramètres de barre de défilement, en incluant les positions minimums et maximums, la taille de la page et la position de la boite de défilement.
SetScrollPos | user32.dll | Cette fonction permet de fixer la position d'une boite de défilement et, si requis, redessiner la barre de défilement reflétant la nouvelle position de la boite de défilement.
SetScrollRange | user32.dll | Cette fonction permet de fixer les valeurs de position minimum et maximum de la barre de défilement spécifié.
SetSecurityDescriptorDacl | advapi32.dll | Cette fonction permet de fixer les informations dans une liste de contrôle d'accès (ACL) discrétionnaire.
SetSecurityDescriptorGroup | advapi32.dll | Cette fonction permet de fixer le groupe primaire d'information d'un format absolue de descripteur de sécurité, remplacer par un groupe d'information primaire déjà présent dans un descripteur de sécurité.
SetSecurityDescriptorOwner | advapi32.dll | Cette fonction permet de fixer les informations de propriétaire d'un format absolue de descripteur de sécurité.
SetSecurityDescriptorSacl | advapi32.dll | Cette fonction permet de fixer les informations d'une liste de contrôle d'accès (ACL) système.
SetStdHandle | kernel32.dll | Cette fonction permet d'utiliser un ensemble de gestionnaire pour l'entrée standard, la sortie standard ou le périphérique d'erreur standard.
SetStretchBltMode | gdi32.dll | Cette fonction permet de fixer le mode d'étirement Bitmap.
SetSysColors | user32.dll | Cette fonction permet de fixer la/les couleurs d'un/des objet(s) affiché(s) du système d'exploitations Windows.
SetSystemCursor | user32.dll | Cette fonction permet de modifier un des curseurs qu'utilise le système d'exploitations Windows.
SetSystemPaletteUse | gdi32.dll | Cette fonction permet de fixer l'utilisation de la couleur statique dans une palette du système.
SetSystemPowerState | kernel32.dll | Cette fonction permet de fixer l'état de l'alimentation en énergie de l'ordinateur.
SetSystemTime | kernel32.dll | Cette fonction permet de fixer la date et l'heure système courante.
SetSystemTimeAdjustment | kernel32.dll | Cette fonction permet d'activer ou désactiver l'ajustement du temps périodique de l'horloge de l'heure de la journée.
SetTapeParameters | kernel32.dll | Cette fonction permet de fixer la taille d'un bloc de bande magnétique ou configure le périphérique de bande magnétique.
SetTapePosition | kernel32.dll | Cette fonction permet de fixer la position de la bande magnétique dans un périphérique spécifié.
SetTextAlign | gdi32.dll | Cette fonction permet de fixer le contexte d'alignement d'un objet de texte.
SetTextColor | gdi32.dll | Cette fonction permet de fixer la couleur du texte de la couleur spécifié.
SetTextCharacterExtra | gdi32.dll | Cette fonction permet de fixer la quantité d'espacement entre les caractères.
SetTextJustification | gdi32.dll | Cette fonction permet d'ajouter de l'espacement dans les caractères de saut d'une chaine de caractères.
SetThreadAffinityMask | kernel32.dll | Cette fonction permet de fixer le masque d'affinité du microprocesseur pour le processus léger spécifié.
SetThreadContext | kernel32.dll | Cette fonction permet de fixer le contexte d'un processus léger spécifié.
SetThreadDesktop | user32.dll | Cette fonction permet de fixer le bureau au processus léger appellant.
SetThreadIdealProcessor | kernel32.dll | Cette fonction permet de fixer le microprocesseur préféré pour un processus léger.
SetThreadLocale | kernel32.dll | Cette fonction permet de fixer l'appel de processus léger locale courant.
SetThreadPriority | kernel32.dll | Cette fonction permet de fixer la valeur de priorité pour le processus léger spécifié.
SetThreadPriorityBoost | kernel32.dll | Cette fonction permet d'activer ou désactiver l'habilité d'un système à temporairement stimuler la priorité d'un processus léger.
SetThreadToken | advapi32.dll | Cette fonction permet de fixer une personnification de jeton à un processus léger.
SetTimer | user32.dll | Cette fonction permet d'installer une minuterie système.
SetTimeZoneInformation | kernel32.dll | Cette fonction permet de fixer les paramètres de fuseau horaire courant.
SetTokenInformation | advapi32.dll | Cette fonction permet de fixer une variété de type d'information pour un accès à un jeton spécifié.
SetUnhandledExceptionFilter | kernel32.dll | Cette fonction permet à une application de remplacer le gestionnaire d'exception de bas niveau dans les places Win32 du haut de chaque processus léger et processus.
SetupComm | kernel32.dll | Cette fonction permet d'initialiser les paramètres de communications pour un périphérique de communications spécifié.
SetupCopyErrorA | setupapi.dll | Cette fonction permet d'afficher une boite de dialogue pour indiquer à l'utilisateur qu'une erreur pendant la copie s'est produit.
SetupDeleteErrorA | setupapi.dll | Cette fonction permet d'afficher une boite de dialogue pour indiquer à l'utilisateur qu'une erreur pendant l'effacement d'un fichier s'est produit.
SetUserObjectInformationA | user32.dll | Cette fonction permet de fixer les informations à propos d'une fenêtre de station ou d'un objet de bureau.
SetUserObjectInformationW | user32.dll | Cette fonction permet de fixer les informations à propos d'une fenêtre de station ou d'un objet de bureau avec des paramètres de chaine de caractères étendues (Unicode).
SetUserObjectSecurity | user32.dll | Cette fonction permet de fixer la sécurité d'un objet utilisateur.
SetViewportExtEx | gdi32.dll | Cette fonction permet de fixer les extensions X et Y d'un point de vue d'un périphérique de contexte spécifié.
SetViewportOrgEx | gdi32.dll | Cette fonction permet de fixer le point de vue d'origine d'un périphérique de contexte spécifié.
SetVolumeLabelA | kernel32.dll | Cette fonction permet de fixer le nom de volume d'une unité de disque.
SetVolumeLabelW | kernel32.dll | Cette fonction permet de fixer le nom de volume d'une unité de disque avec des paramètres de chaine de caractères étendues (Unicode).
SetWaitableTimer | kernel32.dll | Cette fonction permet d'activer la minuterie d'attente spécifié.
SetWindowContextHelpId | user32.dll | Cette fonction permet d'associer un identificateur de contexte d'aide avec la fenêtre spécifié.
SetWindowLongA | user32.dll | Cette fonction permet de placer une valeur 32 bits dans le déplacement spécifié dans une fenêtre en mémoire supplémentaire pour la fenêtre spécifié.
SetWindowLongW | user32.dll | Cette fonction permet de placer une valeur 32 bits dans le déplacement spécifié dans une fenêtre en mémoire supplémentaire pour la fenêtre spécifié avec des paramètres de chaine de caractères étendues (Unicode).
SetWindowPlacement | user32.dll | Cette fonction permet de fixer l'état d'affichage et de restituer, minimiser et maximiser les positions d'une fenêtre.
SetWindowPos | user32.dll | Cette fonction permet de fixer la position d'une fenêtre à une nouvelle coordonnées à l'écran.
SetWindowRgn | user32.dll | Cette fonction permet de fixer la région de fenêtre dans une fenêtre.
SetWindowsHookA | user32.dll | Cette fonction permet d'installer une fonction de crochet utilisateur dans une chaine de crochet.
SetWindowsHookExA | user32.dll | Cette fonction permet d'installer un crochet de fonction utilisateur dans une chaine de crochet.
SetWindowsHookExW | user32.dll | Cette fonction permet d'installer un crochet de fonction utilisateur dans une chaine de crochet avec des paramètres de chaine de caractères étendues (Unicode).
SetWindowsHookW | user32.dll | Cette fonction permet d'installer une fonction de crochet utilisateur dans une chaine de crochet avec des paramètres de chaine de caractères étendues (Unicode).
SetWindowTextA | user32.dll | Cette fonction permet de définir le contenu texte d'une fenêtre.
SetWindowTextW | user32.dll | Cette fonction permet de définir le contenu texte d'une fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
SetWindowWord | user32.dll | Cette fonction permet de placer une valeur 16 bits dans le déplacement spécifié dans une fenêtre en mémoire supplémentaire pour la fenêtre spécifié.
SetWinMetaFileBits | gdi32.dll | Cette fonction permet de convertir un fichier méta d'un ancien format Windows vers un nouveau format étendue et entrepose le fichier méta en mémoire.
SetWindowOrgEx | gdi32.dll | Cette fonction permet de fixer la fenêtre d'origine d'un périphérique de contexte spécifié.
SetWorldTransform | gdi32.dll | Cette fonction permet de fixer une transformation de double dimensions linéaire entre l'espace mondiale et l'espace de page pour le périphérique de contexte spécifié.
SHAddToRecentDocs | shell32.dll | Cette fonction permet d'ajouter un lien vers un document dans la liste des documents récamment utilisé.
SHAppBarMessage | shell32.dll | Cette fonction permet d'envoyer des commandes à la barrre de tâche.
SHBrowseForFolder | shell32.dll | Cette fonction permet d'afficher la boite de dialogue sélectionnant un répertoire.
SHCopyKeyA | shell32.dll | Cette fonction permet de copier les clefs de registre d'un répertoire à un autre dans la base de registres en tenant compte de bien copier toutes les sous-branches de répertoire inclus dans la clef source.
ShellAboutA | shell32.dll | Cette fonction permet d'afficher la boite de dialogue «A propos» de l'application spécifié.
ShellExecuteA | shell32.dll | Cette fonction permet de lancer une application externe.
ShellExecuteEx | shell32.dll | Cette fonction permet de lancer une application externe en utilisant l'interpréteur de commande (Shell) du système d'exploitation Windows.
Shell_NotifyIconA | shell32.dll | Cette fonction permet d'effectuer l'action d'ajouter, modifier ou supprimer une icône dans la barre de tâche.
SHEmptyRecycleBinA | shell32.dll | Cette fonction permet de vider le contenu de la corbeille.
SHFileOperationA | shell32.dll | Cette fonction permet d'effectuer une copie, renommer, déplacer ou de supprimer un objet de fichier.
SHFormatDrive | shell32.dll | Cette fonction permet d'effectuer le formattage d'une unité de disque.
SHGetPathFromIDList | shell32.dll | Cette fonction permet de retourner un pointeur dans un arbre de répertoire à l'emplacement ou se trouve un fichier.
ShowCaret | user32.dll | Cette fonction permet d'afficher un caret à l'écran à la position courant du caret.
ShowCursor | user32.dll | Cette fonction permet d'afficher ou de faire disparaitre le pointeur de la souris.
ShowOwnedPopups | user32.dll | Cette fonction permet d'afficher ou de cacher tous les fenêtres POP-UP du propriétaire de la fenêtre spécifié.
ShowScrollBar | user32.dll | Cette fonction permet d'afficher ou de faire disparaitre la barre de défilement d'une fenêtre ou d'un objet.
ShowWindow | user32.dll | Cette fonction permet de définir l'état visuel d'une fenêtre d'application.
ShowWindowAsync | user32.dll | Cette fonction permet de fixer l'affichage d'état de la création d'une fenêtre par différent processus léger.
SHSetValueA | shlwapi.dll | Cette fonction permet de fixer une valeur dans la base de registres.
SignalObjectAndWait | kernel32.dll | Cette fonction permet de signaler un objet et d'attendre après un autre objet, une simple opération.
SizeofResource | kernel32.dll | Cette fonction permet de demander la taille, en octets, d'une ressource spécifié.
Sleep | kernel32.dll | Cette fonction permet de faire passer en mode d'attente pendant un certain délai en milliseconde le processus courant.
SleepEx | kernel32.dll | Cette fonction permet de faire en sorte que le processus léger courant entre dans un état d'attente jusqu'à ce que l'intervalle de temps spécifié est passé ou jusqu'à l'achèvement d'une fonction d'appel arrière d'entrée/sortie soit appelé.
sndPlaySoundA | winmm.dll | Cette fonction permet de faire jouer un son de format Wave à partir d'un fichier.
SQLAllocConnect | odbc32.dll | Cette fonction permet d'effectuer de l'allocation de mémoire pour un identificateur de connexion ODBC avec un environnement spécifié.
SQLAllocEnv | odbc32.dll | Cette fonction permet d'effectuer de l'allocation de mémoire pour un identificateur d'environnement ODBC.
SQLAllocStmt | odbc32.dll | Cette fonction permet d'effectuer de l'allocation de mémoire pour un identificateur d'instruction ODBC.
SQLBindCol | odbc32.dll | Cette fonction permet d'effectuer de la lecture d'une colonne du résultat d'une requête par ODBC.
SQLBindParameter | odbc32.dll | Cette fonction permet d'envoyer un tampon de paramètres dans une instruction SQL par ODBC.
SQLConfigDataSource | odbccp32.dll | Cette fonction permet d'envoyer des commandes au pilote ODBC (Open Data Base Connectivity).
StartDocA | gdi32.dll | Cette fonction permet d'indiquer le commencement d'un travail d'impression pour l'imprimante.
StartDocW | gdi32.dll | Cette fonction permet d'indiquer le commencement d'un travail d'impression pour l'imprimante avec des paramètres de chaine de caractères étendues (Unicode).
StartPage | gdi32.dll | Cette fonction permet d'indiquer le début d'une page de document pour l'impression pour l'imprimante.
StretchBlt | gdi32.dll | Cette fonction permet de copier un morceau dans une autre image.
StretchDIBits | gdi32.dll | Cette fonction permet de copier les données de couleur d'un rectangle de pixels dans un DIB de la destination rectangulaire spécifié.
StrIsIntlEqualA | shlwapi.dll | Cette fonction permet de comparer le début de deux chaines de caractères ASCIZ.
StrNCatA | shlwapi.dll | Cette fonction permet d'ajouter à la fin d'une chaine de caractères ASCIZ le contenu d'une chaine de caractères ASCIZ.
StrokeAndFillPath | gdi32.dll | Cette fonction permet de fermer n'importe quel figures ouvertes dans un chemin, traitant les lignes en dehors du chemin en utilisant le crayon courant, et remplissant l'intérieur en utilisant la brosse.
StrokePath | gdi32.dll | Cette fonction permet d'effectuer un traitement d'un chemin spécifié en utilisant le crayon courant.
StrToIntExA | shlwapi.dll | Cette fonction permet de convertir une chaine de caractères ASCIZ ayant un nombre décimal ou hexadécimal en une valeur numérique entière.
StrTrimA | shlwapi.dll | Cette fonction permet d'enlever les caractères spécifié au début et à la fin d'une chaine de caractères ASCIZ.
SubtractRect | user32.dll | Cette fonction permet de demander les coordonnées d'un rectangle par soustraction d'un rectangle par un autre.
SuspendThread | kernel32.dll | Cette fonction permet de suspendre le processus léger spécifié.
SwapBuffers | gdi32.dll | Cette fonction permet d'échanger le tampon de premier plan avec l'arrière-plan si le format du pixel courant pour la fenêtre est référencée par le périphérique de contexte spécifié inclus dans le tampon d'arrière plan.
SwapMouseButton | user32.dll | Cette fonction permet d'échanger le comportement des boutons de gauche de la souris devienne celui de droite et que celui de droite devienne celui de gauche.
SwitchDesktop | user32.dll | Cette fonction permet de fixer le bureau visible et actif.
SwitchToFiber | kernel32.dll | Cette fonction permet de planifier une fibre.
SwitchToThread | kernel32.dll | Cette fonction permet de provoquer l'exécution d'un rendement de processus léger vers un autre processus léger étant prêt a être exécuté par le microprocesseur courant.
SystemParametersInfoA | user32.dll | Cette fonction permet de fixer certain paramètre du système d'exploitation Windows.
SystemParametersInfoW | user32.dll | Cette fonction permet de fixer certain paramètre du système d'exploitation Windows avec des paramètres de chaine de caractères étendues (Unicode).
SystemTimeToFileTime | kernel32.dll | Cette fonction permet de convertir le temps système vers le temps fichier.
SystemTimeToTzSpecificLocalTime | kernel32.dll | Cette fonction permet de convertir l'UTC (Coordinated Universal Time) vers un fuseau horaire spécifié correspondant au temps locale.
TabbedTextOutA | user32.dll | Cette fonction permet d'écrire une chaine de caractères à l'emplacement spécifié, en étendant la tabulation des valeurs spécifiés dans des positions d'une tableau de tabulation sans arrêt.
TabbedTextOutW | user32.dll | Cette fonction permet d'écrire une chaine de caractères à l'emplacement spécifié, en étendant la tabulation des valeurs spécifiés dans des positions d'une tableau de tabulation sans arrêt avec des paramètres de chaine de caractères étendues (Unicode).
TerminateProcess | kernel32.dll | Cette fonction permet de terminer le processus spécifié et tous ses processus légers.
TerminateThread | kernel32.dll | Cette fonction permet de terminer le processus léger spécifié.
TextOutA | gdi32.dll | Cette fonction permet d'afficher une chaine de caractères dans une fenêtre d'application.
TextOutW | gdi32.dll | Cette fonction permet d'afficher une chaine de caractères dans une fenêtre d'application avec des paramètres de chaine de caractères étendues (Unicode).
TileWindows | user32.dll | Cette fonction permet de présenter sous forme mosaïque les sous-fenêtre d'une fenêtre d'application MDI.
TlsAlloc | kernel32.dll | Cette fonction permet d'allouer un processus léger d'entrepose d'index locale (TLS).
TlsFree | kernel32.dll | Cette fonction permet de libérer un processus léger d'entrepose d'index locale (TLS).
TlsGetValue | kernel32.dll | Cette fonction permet de demander la valeur dans un appel de processus d'entreposage d'une fente locale (TLS) pour un index TLS spécifié.
TlsSetValue | kernel32.dll | Cette fonction permet d'entreposer la valeur d'un appel de processus d'entreposage d'une fente locale (TLS) pour un index TLS spécifié.
ToAscii | user32.dll | Cette fonction permet de traduire le code de touche virtuel spécifié et l'état du clavier en sa correspondance de caractère Windows ou de caractères.
ToAsciiEx | user32.dll | Cette fonction permet de traduire le code de touche virtuel spécifié et l'état du clavier en sa correspondance de caractère Windows ou de caractères avec des options supplémentaires.
TouchFileTimes | imagehlp.dll | Cette fonction permet de fixer la date et l'heure d'un fichier avec un Handle.
ToUnicode | user32.dll | Cette fonction permet de traduire le code de touche virtuel spécifié et l'état du clavier en sa correspondance de caractère Unicode ou de caractères.
ToUnicodeEx | user32.dll | Cette fonction permet de traduire le code de touche virtuel spécifié et l'état du clavier en sa correspondance de caractère Unicode ou de caractères avec des options supplémentaires.
TrackMouseEvent | user32.dll | Cette fonction permet de poster les messages lorsque le pointeur de souris laisse une fenêtre ou passe dessus une fenêtre pour un nombre de temps spécifié.
TrackPopupMenu | user32.dll | Cette fonction permet d'afficher un menu contextuel.
TrackPopupMenuEx | user32.dll | Cette fonction permet d'afficher un menu contextuel à l'emplacement spécifié et trace les items de sélection dans un menu contextuel.
TransactNamedPipe | kernel32.dll | Cette fonction permet de combiner dans une simple opération réseau les fonctions d'écriture d'un message et de lecture de message d'un tuyau nommé spécifié.
TranslateAcceleratorA | user32.dll | Cette fonction permet d'effectuer le traitement des touches d'accélérations pour les commandes de menu.
TranslateAcceleratorW | user32.dll | Cette fonction permet d'effectuer le traitement des touches d'accélérations pour les commandes de menu avec des paramètres de chaine de caractères étendues (Unicode).
TranslateCharsetInfo | user32.dll | Cette fonction permet d'effectuer une conversion, basé sur ensemble de caractères spécifié, un code de page ou une valeur de signature de police de caractères, ajustant tous les membres d'une structure de destination aux valeurs approprié.
TranslateMDISysAccel | user32.dll | Cette fonction permet d'effectuer le traitement d'une combinaison de touches d'accélération pour une interface de document multiple (MDI) de la fenêtre enfant.
TranslateMessage | user32.dll | Cette fonction permet d'effectuer une transformation de message de touche virtuel en caractères de messages.
TransmitCommChar | kernel32.dll | Cette fonction permet de transmettre un caractère spécifié à venir dans des données en attente dans un tampon de sortie du périphérique de communication spécifié.
TryEnterCriticalSection | kernel32.dll | Cette fonction permet d'effectuer une tentative pour entrer dans une section critique sans blocage.
UnhandledExceptionFilter | kernel32.dll | Cette fonction permet de passer une exception non définit au débogueur si le processus de déboguage est actif.
UnhookWindowsHook | user32.dll | Cette fonction permet d'enlever la fonction crochet utilisateur pour une chaine de fonctions crochet.
UnhookWindowsHookEx | user32.dll | Cette fonction permet d'enlever un crochet de fonction utilisateur d'une chaine de crochet.
UnionRect | user32.dll | Cette fonction permet d'effectuer la création de l'union de 2 rectangles.
UnloadKeyboardLayout | user32.dll | Cette fonction permet d'enlever une schéma de clavier.
UnlockFile | kernel32.dll | Cette fonction permet de déverrouiller une région d'un fichier ouvert.
UnlockFileEx | kernel32.dll | Cette fonction permet de déverrouiller une rangée d'octets précédemment verrouillé dans un fichier ouvert.
UnmapViewOfFile | kernel32.dll | Cette fonction permet de cartographier une vue cartographier dans un fichier d'un espace d'adressage d'un processus appelant.
UnpackDDElParam | user32.dll | Cette fonction permet d'effectuer un décompactage d'une valeur DDE reçu spécifié vers un message DDE posté.
UnrealizeObject | gdi32.dll | Cette fonction permet de réinitialiser l'origine de la brosse ou de réinitialiser la palette logique.
UnregisterClassA | user32.dll | Cette fonction permet d'enlever une classe de fenêtre et libère l'entreposage requis par la classe.
UnregisterClassW | user32.dll | Cette fonction permet d'enlever une classe de fenêtre et libère l'entreposage requis par la classe avec des paramètres de chaine de caractères étendues (Unicode).
UnregisterDeviceNotification | user32.dll | Cette fonction permet de désenregistrer un périphérique ou un type de périphérique pour que la fenêtre reçoivent des avertissements de changements.
UnregisterHotKey | user32.dll | Cette fonction permet de désenregistrer l'état des touches de combinaison (Alt, Ctrl, Shift) pour le processus léger.
UpdateColors | gdi32.dll | Cette fonction permet de mettre-à-jour la région client d'un périphérique de contexte spécifié en faisant correspondre les couleurs courantes dans une région client, pixel par pixel, à une palette de système.
UpdateResourceA | kernel32.dll | Cette fonction permet d'ajouter, supprimer ou remplacer une ressource dans un fichier exécutable.
UpdateResourceW | kernel32.dll | Cette fonction permet d'ajouter, supprimer ou remplacer une ressource dans un fichier exécutable avec des paramètres de chaine de caractères étendues (Unicode).
UpdateWindow | user32.dll | Cette fonction permet de mettre à jour la région client de la fenêtre donnée par l'envoie du message WM_PAINT à la fenêtre si la région à mettre à jour de la fenêtre n'est pas vide.
UrlCanonicalizeA | shlwapi.dll | Cette fonction permet de transformer un chemin d'un URL de sa forme absolue à relative, c'est-à-dire de remplacer des "../" pour atteinte le répertoire en court par exemple.
UrlCombineA | shlwapi.dll | Cette fonction permet d'ajouter un chemin d'un URL à la fin d'un autre chemin d'URL.
UrlCreateFromPathA | shlwapi.dll | Cette fonction permet de convertir un chemin de système d'exploitation de style DOS sous sa forme URL.
UrlEscapeA | shlwapi.dll | Cette fonction permet de convertir les caractères particulier en format acceptable pour un URL.
UrlGetPartA | shlwapi.dll | Cette fonction permet d'extraire une partie de l'URL.
UrlIsOpaqueA | shlwapi.dll | Cette fonction permet de demander si le URL est opaque.
UrlUnescapeA | shlwapi.dll | Cette fonction permet de convertir les caractères particulier du URL en une chaine de caractères ASCIZ.
ValidateRect | user32.dll | Cette fonction permet de valider une région client avec le rectangle donnée par suppression du rectangle de la région à mettre à jour de la fenêtre donnée.
ValidateRgn | user32.dll | Cette fonction permet de valider la région client avec la région donnée par suppression de la région de la mise à jour courante d'une région de fenêtre spécifié.
VerifyScreenSavePwd | password.cpl | Cette fonction permet d'effectuer la vérification du mots de passe lors du lancement d'un écran de veille.
VerLanguageNameA | kernel32.dll | Cette fonction permet de demander le MID identifiant le langage utilisé par le système d'exploitation.
VerLanguageNameW | kernel32.dll | Cette fonction permet de demander le MID identifiant le langage utilisé par le système d'exploitation avec des paramètres de chaines de caractères étendues (Unicode).
VirtualAlloc | kernel32.dll | Cette fonction permet de réservé ou d'engager une région de pages dans un espace d'adressage virtuel d'un processus appelant.
VirtualAllocEx | kernel32.dll | Cette fonction permet de réservé ou d'engager une région de pages dans un espace d'adressage virtuel pour le processus spécifié.
VirtualFree | kernel32.dll | Cette fonction permet de relâcher ou désengager (ou les deux) une région de pages dans une espace de d'adressage virtuel d'un processus appelant.
VirtualFreeEx | kernel32.dll | Cette fonction permet de relâcher ou désengager (ou les deux) une région de pages dans une espace de d'adressage virtuel pour un processus spécifié.
VirtualLock | kernel32.dll | Cette fonction permet de verrouiller une région spécifié dans un espace d'adressage virtuel de la mémoire en s'assurant que l'accès ultérieur à la région ne soit pas passible d'une erreur de page.
VirtualQuery | kernel32.dll | Cette fonction permet de fournir les informations à propos d'un rang de pages dans un espace d'adressage virtuel pour un processus appelant.
VirtualQueryEx | kernel32.dll | Cette fonction permet de fournir les informations à propos d'un rang de pages dans un espace d'adressage virtuel dans un processus spécifié.
VirtualUnlock | kernel32.dll | Cette fonction permet de déverrouiller un range de pages spécifié dans un espace d'adressage virtuel d'un processus et activant le système pour l'échange de pages dans le fichier de pagination si nécessaire.
VkKeyScanA | user32.dll | Cette fonction permet d'effectuer la conversion de caractère correspondant un code de touche virtuel et de l'état de touche de combinaison du clavier courant.
VkKeyScanExA | user32.dll | Cette fonction permet d'effectuer la conversion de caractère correspondant un code de touche virtuel et de l'état de touche de combinaison du clavier courant en utilisant le langage et la couche physique du clavier.
VkKeyScanExW | user32.dll | Cette fonction permet d'effectuer la conversion de caractère correspondant un code de touche virtuel et de l'état de touche de combinaison du clavier courant en utilisant le langage et la couche physique du clavier avec des paramètres de chaine de caractères étendues (Unicode).
VkKeyScanW | user32.dll | Cette fonction permet d'effectuer la conversion de caractère correspondant un code de touche virtuel et de l'état de touche de combinaison du clavier courant avec des paramètres de chaine de caractères étendues (Unicode).
WaitCommEvent | kernel32.dll | Cette fonction permet d'attendre qu'un événement se produise sur le périphérique de communication spécifié.
WaitForDebugEvent | kernel32.dll | Cette fonction permet d'attendre qu'un événement de déboguage se produise dans un processus en déboguage.
WaitForInputIdle | user32.dll | Cette fonction permet d'attendre tant que le processus données est en attente d'une entrée utilisateur sans attendre d'entrée ou de dépassement d'intervalle de temps.
WaitForMultipleObjects | kernel32.dll | Cette fonction permet de retourner lorsque l'un des objets indiqués est dans l'état signalé, ou lorsque le délai d'attente spécifié s'est écoulé.
WaitForMultipleObjectsEx | kernel32.dll | Cette fonction permet d'attendre que la fonction soit utilisés pour un traitement d'attendre d'alerte.
WaitForSingleObject | kernel32.dll | Cette fonction permet d'attendre qu'un objet retourne un signal ou que le délai spécifié soit écoulé.
WaitForSingleObjectEx | kernel32.dll | Cette fonction permet d'étendre la fonction d'attente étant utilisés pour un traitement d'attendre d'alerte.
WaitMessage | user32.dll | Cette fonction permet de rendre le contrôle à une autre application quand une application n'a pas une autre tâche en traitement.
WaitNamedPipeA | kernel32.dll | Cette fonction permet d'attendre jusqu'à ce qu'un dépassement d'intervalle ou d'une instance d'un tuyau nommé spécifié soit disponible pour être connecté à processus en attente d'une opération ConnectNamedPipe se produise.
WaitNamedPipeW | kernel32.dll | Cette fonction permet d'attendre jusqu'à ce qu'un dépassement d'intervalle ou d'une instance d'un tuyau nommé spécifié soit disponible pour être connecté à processus en attente d'une opération ConnectNamedPipe se produise avec des paramètres de chaine de caractères étendue (Unicode).
waveOutGetNumDevs | winmm.dll | Cette fonction permet de demander le nombre de sortie Wave disponible sur le système d'explotation Windows.
WideCharToMultiByte | kernel32.dll | Cette fonction permet de cartographier une chaine de caractères Unicode en une nouvelle chaine de caractères.
WidenPath | gdi32.dll | Cette fonction permet de redéfinir le chemin courant de la région devant être repeinte si le chemin est un trait utilisant le crayon courant sélectionné dans le périphérique de contexte spécifié.
WindowFromDC | user32.dll | Cette fonction permet de demander le Handle de la fenêtre correspondante au périphérique de contexte spécifié.
WindowFromPoint | user32.dll | Cette fonction permet de demander le Handle associé à une fenêtre correspondant au coordonnées pixel spécifié.
WinExec | kernel32.dll | Cette fonction permet de lancer l'exécution d'un programme externe.
WinHelpA | user32.dll | Cette fonction permet de démarrer la fenêtre d'aide (WINHELP.EXE) et passe les données optionnels indiquant la nature de la requête d'aide pour l'application.
WinHelpW | user32.dll | Cette fonction permet de démarrer la fenêtre d'aide (WINHELP.EXE) et passe les données optionnels indiquant la nature de la requête d'aide pour l'application avec des paramètres de chaine de caractères étendues (Unicode).
WNetAddConnection2A | mpr.dll | Cette fonction permet de fabriquer une connexion vers une ressource réseau.
WNetAddConnection2W | mpr.dll | Cette fonction permet de fabriquer une connexion vers une ressource réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetAddConnection3A | mpr.dll | Cette fonction permet de fabriquer une connexion vers une ressource réseau en utilisant des options supplémentaires.
WNetAddConnection3W | mpr.dll | Cette fonction permet de fabriquer une connexion vers une ressource réseau en utilisant des options supplémentaires avec des paramètres de chaine de caractères étendues (Unicode).
WNetAddConnectionA | mpr.dll | Cette fonction permet d'effectuer la connexion d'une unité de disque à une ressource réseau.
WNetAddConnectionW | mpr.dll | Cette fonction permet d'effectuer la connexion d'une unité de disque à une ressource réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetCancelConnection2A | mpr.dll | Cette fonction permet d'effectuer l'arrêt d'une connexion réseau existante.
WNetCancelConnection2W | mpr.dll | Cette fonction permet d'effectuer l'arrêt d'une connexion réseau existante avec des paramètres de chaine de caractères étendues (Unicode).
WNetCancelConnectionA | mpr.dll | Cette fonction permet d'effectuer la déconnexion d'une unité de disque à une ressource réseau.
WNetCancelConnectionW | mpr.dll | Cette fonction permet d'effectuer la déconnexion d'une unité de disque à une ressource réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetCloseEnum | mpr.dll | Cette fonction permet de terminer l'énumération d'une ressource réseau débuté par la fonction WNetOpenEnum.
WNetConnectionDialog | mpr.dll | Cette fonction permet d'afficher une boite de dialogue permettant à une unité de disque de se connecter à une ressource réseau.
WNetConnectionDialog1A | mpr.dll | Cette fonction permet d'introduire une boite de dialogue de navigation général pour la connexion des ressources de réseau.
WNetConnectionDialog1W | mpr.dll | Cette fonction permet d'introduire une boite de dialogue de navigation général pour la connexion des ressources de réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetDisconnectDialog | mpr.dll | Cette fonction permet d'afficher une boite de dialogue permettant à une unité de disque de se déconnecter à une ressource réseau.
WNetDisconnectDialog1A | mpr.dll | Cette fonction permet d'effectuer la déconnexion d'une ressource de réseau.
WNetDisconnectDialog1W | mpr.dll | Cette fonction permet d'effectuer la déconnexion d'une ressource de réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetEnumResourceA | mpr.dll | Cette fonction permet de continuer l'énumération de ressource réseau commencé par la fonction WNetOpenEnum.
WNetEnumResourceW | mpr.dll | Cette fonction permet de continuer l'énumération de ressource réseau commencé par la fonction WNetOpenEnum avec des paramètres de chaine de caractères étendues (Unicode).
WNetGetConnectionA | mpr.dll | Cette fonction permet de demander le nom de la ressource réseau associé avec un périphérique local.
WNetGetConnectionW | mpr.dll | Cette fonction permet de demander le nom de la ressource réseau associé avec un périphérique local avec des paramètres de chaine de caractères étendues (Unicode).
WNetGetLastErrorA | mpr.dll | Cette fonction permet de demander le plus récent des derniers code d'erreur étendue des fonctions réseau du système d'exploitation.
WNetGetLastErrorW | mpr.dll | Cette fonction permet de demander le plus récent des derniers code d'erreur étendue des fonctions réseau du système d'exploitation avec des paramètres de chaine de caractères étendues (Unicode).
WNetGetNetworkInformationA | mpr.dll | Cette fonction permet de demander les informations étendues à propos d'un fournisseur de réseau spécifié dont le nom a été retourné lors d'une énumération réseau précédente.
WNetGetNetworkInformationW | mpr.dll | Cette fonction permet de demander les informations étendues à propos d'un fournisseur de réseau spécifié dont le nom a été retourné lors d'une énumération réseau précédente avec des paramètres de chaine de caractères étendues (Unicode).
WNetGetProviderNameA | mpr.dll | Cette fonction permet de demander le nom de fournisseur pour un type spécifié dans un réseau.
WNetGetProviderNameW | mpr.dll | Cette fonction permet de demander le nom de fournisseur pour un type spécifié dans un réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetGetResourceParentA | mpr.dll | Cette fonction permet de naviguer à partir d'une ressource.
WNetGetResourceParentW | mpr.dll | Cette fonction permet de naviguer à partir d'une ressource avec des paramètres de chaine de caractères étendues (Unicode).
WNetGetUniversalNameA | mpr.dll | Cette fonction permet de prendre un chemin, avec comme base une unité de disque, pour un réseau de ressource et demande la structure de données contenant des formes universel de ce nom.
WNetGetUniversalNameW | mpr.dll | Cette fonction permet de prendre un chemin, avec comme base une unité de disque, pour un réseau de ressource et demande la structure de données contenant des formes universel de ce nom avec des paramètres de chaine de caractères étendues (Unicode).
WNetGetUserA | mpr.dll | Cette fonction permet de connaitre le nom de l'utilisateur utilisé pour la connexion réseau.
WNetGetUserW | mpr.dll | Cette fonction permet de connaitre le nom de l'utilisateur utilisé pour la connexion réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetOpenEnumA | mpr.dll | Cette fonction permet de démarrer l'énumération de ressource réseau ou de connexions existantes.
WNetOpenEnumW | mpr.dll | Cette fonction permet de démarrer l'énumération de ressource réseau ou de connexions existantes avec des paramètres de chaine de caractères étendues (Unicode).
WNetSetConnectionA | mpr.dll | Cette fonction permet de modifier les caractéristiques d'une connexion réseau.
WNetSetConnectionW | mpr.dll | Cette fonction permet de modifier les caractéristiques d'une connexion réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetUseConnectionA | mpr.dll | Cette fonction permet de fabriquer une connexion vers une ressource réseau.
WNetUseConnectionW | mpr.dll | Cette fonction permet de fabriquer une connexion vers une ressource réseau avec des paramètres de chaine de caractères étendues (Unicode).
WriteConsoleA | kernel32.dll | Cette fonction permet d'effectuer l'écriture d'une chaine de caractères dans la sortie de la console standard.
WriteConsoleW | kernel32.dll | Cette fonction permet d'effectuer l'écriture d'une chaine de caractères dans la sortie de la console standard avec des paramètres de chaine de caractères étendues (Unicode).
WriteConsoleInputA | kernel32.dll | Cette fonction permet d'écrire des données directement dans l'entrée du tampon de la console.
WriteConsoleInputW | kernel32.dll | Cette fonction permet d'écrire des données directement dans l'entrée du tampon de la console avec des paramètres de chaine de caractères étendues (Unicode).
WriteConsoleOutputA | kernel32.dll | Cette fonction permet d'écrire le caractère et la donnée d'attribut de couleur vers un bloc rectangulaire spécifié dans une cellule de caractères d'un tampon de console d'écran.
WriteConsoleOutputW | kernel32.dll | Cette fonction permet d'écrire le caractère et la donnée d'attribut de couleur vers un bloc rectangulaire spécifié dans une cellule de caractères d'un tampon de console d'écran avec des paramètres de chaine de caractères étendue (Unicode).
WriteConsoleOutputAttribute | kernel32.dll | Cette fonction permet de copier un nombre d'attributs de couleurs de premier plan et d'arrière-plan dans une cellule consécutive d'un tampon de console d'écran en débutant à l'emplacement spécifié.
WriteConsoleOutputCharacterA | kernel32.dll | Cette fonction permet d'effectuer une copie d'un nombre de caractères à une cellule consécutive d'un tampon de console d'écran en débutant à l'emplacement spécifié.
WriteConsoleOutputCharacterW | kernel32.dll | Cette fonction permet d'effectuer une copie d'un nombre de caractères à une cellule consécutive d'un tampon de console d'écran en débutant à l'emplacement spécifié avec des paramètres de chaine de caractères étendue (Unicode).
WriteFile | kernel32.dll | Cette fonction permet d'écrire des données dans un fichier et est conçue pour des opérations sychronisé ou désychronisé.
WriteFileEx | kernel32.dll | Cette fonction permet d'écrire des données dans un fichier.
WritePrivateProfileSectionA | kernel32.dll | Cette fonction permet de remplacer les clefs et les valeurs dans la section spécifié d'un fichier d'initialisation.
WritePrivateProfileSectionW | kernel32.dll | Cette fonction permet de remplacer les clefs et les valeurs dans la section spécifié d'un fichier d'initialisation avec des paramètres de chaine de caractères étendue (Unicode).
WritePrivateProfileStringA | kernel32.dll | Cette fonction permet de copier une chaine de caractères dans une section spécifié d'un fichier d'initialisation spécifié.
WritePrivateProfileStringW | kernel32.dll | Cette fonction permet de copier une chaine de caractères dans une section spécifié d'un fichier d'initialisation spécifié avec des paramètres de chaine de caractères étendue (Unicode).
WriteProcessMemory | kernel32.dll | Cette fonction permet d'écrire en mémoire pour le processus spécifié.
WriteProfileSectionA | kernel32.dll | Cette fonction permet de remplacer le contenu de la section spécifié dans le fichier «WIN.INI» avec les clefs et les valeurs spécifiés.
WriteProfileSectionW | kernel32.dll | Cette fonction permet de remplacer le contenu de la section spécifié dans le fichier «WIN.INI» avec les clefs et les valeurs spécifiés et avec les paramètres de chaine de caractères étendues (Unicode).
WriteProfileStringA | kernel32.dll | Cette fonction permet de copier une chaine de caractères dans la section spécifié d'un fichier «WIN.INI».
WriteProfileStringW | kernel32.dll | Cette fonction permet de copier une chaine de caractères dans la section spécifié d'un fichier «WIN.INI» avec des paramètres de chaine de caractères étendues (Unicode).
WriteTapemark | kernel32.dll | Cette fonction permet d'écrire une marque de fichier, un fixeur de marque, une marque de fichier court ou une long marque de fichier dans un périphérique de bande magnétique.
wsprintfA | user32.dll | Cette fonction permet d'écrire des données formatté d'une chaine de caractères vers un tampon.
wsprintfW | user32.dll | Cette fonction permet d'écrire des données formatté d'une chaine de caractères vers un tampon avec des paramètres de chaine de caractères étendues (Unicode).
wvsprintfA | user32.dll | Cette fonction permet de formatter et d'entreposer une série de caractères et de valeurs virtuel dans un tampon.
wvsprintfW | user32.dll | Cette fonction permet de formatter et d'entreposer une série de caractères et de valeurs virtuel dans un tampon avec des paramètres de chaine de caractères étendues (Unicode).
_hread | kernel32.dll | Cette fonction permet d'effectuer la lecture de données dans le fichier spécifié.
_hwrite | kernel32.dll | Cette fonction permet d'effectuer l'écriture de données dans le fichier spécifié.
_lclose | kernel32.dll | Cette fonction permet de fermer le fichier Handle spécifié.
_lcreat | kernel32.dll | Cette fonction permet de créer ou ouvrir un fichier spécifié.
_llseek | kernel32.dll | Cette fonction permet de positionner le pointeur du fichier Handle spécifié.
_lread | kernel32.dll | Cette fonction permet d'effectuer la lecture de données dans un fichier spécifié.

Voir également

Articles - Les géants de l'informatique - Microsoft

Dernière mise à jour : Samedi, 5 décembre 2015


C pour Windows - API de Windows avec prototypes

Reference API pour C Windows Prototype

Vu dans ... http://www.gladir.com

Nom de l'API | Prototype | Bibliothèque | Description
AbnormalTermination | BOOL AbnormalTermination(VOID); |  | Cette fonction permet d'indiquer que le «TRY-FINALLY» d'un bloc s'est exécuté normalement.
AbortDoc | int AbortDoc(HDC hdc); | gdi32.dll | Cette fonction permet d'effectuer l'annulation d'une impression d'un document.
AbortPath | BOOL AbortPath(HDC hdc); | gdi32.dll | Cette fonction permet de terminer et de restituer toutes les palettes du périphérique de contexte.
AbortPrinter | BOOL AbortPrinter(HANDLE hPrinter); | winspool.drv | Cette fonction permet d'effacer un fichier dans le tampon d'impression si l'imprimante est configuré avec un tampon d'impression.
AbortSystemShutdownA | BOOL AbortSystemShutdownA(LPSTR lpMachineName); | advapi32.dll | Cette fonction permet d'annuler l'opération de fermeture ou de redémarrage en cours d'exécution du système d'exploitation Windows.
AbortSystemShutdownW | BOOL AbortSystemShutdownW(LPWSTR lpMachineName) | advapi32.dll | Cette fonction permet d'annuler l'opération de fermeture ou de redémarrage en cours d'exécution du système d'exploitation Windows avec des paramètres de chaine de caractères étendue (Unicode).
AccessCheck | BOOL AccessCheck( PSECURITY_DESCRIPTOR pSecurityDescriptor, HANDLE ClientToken, DWORD DesiredAccess, PGENERIC_MAPPING GenericMapping, PPRIVILEGE_SET PrivilegeSet, LPDWORD PrivilegeSetLength, LPDWORD GrantedAccess, LPBOOL AccessStatus ); | advapi32.dll | Cette fonction permet, à une application serveur, de vérifier les accès client à un objet de contrôle d'accès.
AccessCheckAndAuditAlarmA | BOOL AccessCheckAndAuditAlarmA( LPCTSTR SubsystemName, LPVOID HandleId, LPTSTR ObjectTypeName, LPTSTR ObjectName, PSECURITY_DESCRIPTOR SecurityDescriptor, DWORD DesiredAccess, PGENERIC_MAPPING GenericMapping, BOOL ObjectCreation, LPDWORD GrantedAccess, LPBOOL AccessStatus, LPBOOL pfGenerateOnClose ); | advapi32.dll | Cette fonction permet d'effectuer une validation d'accès et de provoquer le message approprié.
AccessCheckAndAuditAlarmW | BOOL AccessCheckAndAuditAlarmW( LPCWSTR SubsystemName, LPVOID HandleId, LPWSTR ObjectTypeName, LPWSTR ObjectName, PSECURITY_DESCRIPTOR SecurityDescriptor, DWORD DesiredAccess, PGENERIC_MAPPING GenericMapping, BOOL ObjectCreation, LPDWORD GrantedAccess, LPBOOL AccessStatus, LPBOOL pfGenerateOnClose); | advapi32.dll | Cette fonction permet d'effectuer une validation d'accès et de provoquer le message approprié avec des paramètres de chaine de caractères étendue (Unicode).
AcsLan | ACSLAN_STATUS AcsLan(PLLC_CCB pCcb,PLLC_CCB * ppBadCcb); | dlcapi.dll | Cette fonction permet de communiquer avec d'autres ordinateurs ou par périphérique réseau, comme des imprimantes, en utilisant le protocole DLC (Data Link Control).
ActivateKeyboardLayout | HKL ActivateKeyboardLayout(HKL hkl,UINT Flags); | user32.dll | Cette fonction permet d'activer une disposition différente du clavier pour le processus courant.
AddAccessAllowedAce | BOOL AddAccessAllowedAce(PACL pAcl, DWORD dwAceRevision, DWORD AccessMask, PSID pSid); | advapi32.dll | Cette fonction permet d'ajouter un accès autorisé à l'ACE d'un ACL.
AddAccessDeniedAce | BOOL AddAccessDeniedAce(PACL pAcl, DWORD dwAceRevision, DWORD AccessMask,PSID pSid); | advapi32.dll | Cette fonction permet d'ajouter un accès refusé à l'ACE d'un ACL.
AddAce | BOOL AddAce(PACL pAcl, DWORD dwAceRevision, DWORD dwStartingAceIndex, LPVOID pAceList, DWORD nAceListLength); | advapi32.dll | Cette fonction permet d'ajouter un ACE à un ACL spécifié.
AddAtomA | ATOM AddAtomA(LPCSTR lpString); | kernel32.dll | Cette fonction permet d'ajouter une chaine de caractères ASCIZ à l'Atom global.
AddAtomW | ATOM AddAtomW(LPCWSTR lpString); | kernel32.dll | Cette fonction permet d'ajouter un caractère étendue (Unicode) à l'Atom global.
AddAuditAccessAce | BOOL AddAuditAccessAce(PACL pAcl,DWORD dwAceRevision,DWORD dwAccessMask,PSID pSid,BOOL bAuditSuccess,BOOL bAuditFailure); | advapi32.dll | Cette fonction permet d'ajouter un ACE système à un ACL système.
AddFontResourceA | int AddFontResourceA(LPCTSTR lpszFilename); | gdi32.dll | Cette fonction permet d'ajouter une police de caractères à partir d'un fichier.
AddFontResourceW | int AddFontResourceW(LPCWSTR lpszFilename); | gdi32.dll | Cette fonction permet d'ajouter une police de caractères à partir d'un fichier avec des paramètres de chaine de caractères étendues (Unicode).
AddFormA | BOOL AddFormA(HANDLE hPrinter,DWORD Level,LPBYTE pForm); | winspool.drv | Cette fonction permet d'ajouter un formulaire à la liste des formulaires de l'imprimante spécifié.
AddInksetInterval | BOOL AddInksetInterval(HINKSET hinkset, LPINTERVAL lpiNew); | penwin32.dll | Cette fonction permet de fixer l'intervalle de la cartouche d'encre de l'imprimante.
AddJobA | BOOL AddJobA(HANDLE hPrinter, DWORD Level, LPBYTE pData, DWORD cbBuf, LPDWORD pcbNeeded ); | winspool.drv | Cette fonction permet de fixer la chaine de caractères du répertoire contenant les fichiers du tampon d'imprimante.
AddMonitorA | BOOL AddMonitorA( LPTSTR pName, DWORD Level, LPBYTE pMonitors ); | winspool.drv | Cette fonction permet d'installer un moniteur d'imprimante local et des liens de configuration, de données et de fichiers du moniteur.
AddPenDataHRC | int AddPenDataHRC(HRC hrc, HPENDATA hpndt); | penwin32.dll | Cette fonction permet d'ajouter un objet de données de crayon limineux.
AddPenInputHRC | int AddPenInputHRC( HRC hrc, LPPOINT lppt, LPVOID lpvOem, UINT fuOem, LPSTROKEINFO lpsi ); | penwin32.dll | Cette fonction permet d'ajouter un objet de données de crayon limineux exporté.
AddPointsPenData | HPENDATA AddPointsPenData( HPENDATA hpendata, LPPOINT lppt, LPVOID lpvOem, LPSTROKEINFO lpsiNew ) | penwin32.dll | Cette fonction permet d'ajouter un ensemble de point de données à l'objet de crayon limineux.
AddPortA | BOOL AddPortA(LPTSTR pName, HWND hWnd, LPTSTR pMonitorName); | winspool.drv | Cette fonction permet d'ajouter un nom de port à la liste des ports supporté par l'imprimante.
AddPrinterA | HANDLE AddPrinterA( LPTSTR pName, DWORD Level, LPBYTE pPrinter ); | winspool.drv | Cette fonction permet d'ajouter une imprimante à la liste des imprimantes.
AddPrinterConnectionA | BOOL AddPrinterConnectionA(LPTSTR pName); | winspool.drv | Cette fonction permet d'ajouter une connexion à l'imprimante pour l'utilisateur courant.
AddPrinterDriverA | BOOL AddPrinterDriverA( LPTSTR pName, DWORD Level, LPBYTE pDriverInfo ); | winspool.drv | Cette fonction permet d'installer un pilote d'imprimante avec la configuration, les données et les fichiers du pilote.
AddPrintProcessorA | BOOL AddPrintProcessorA( LPTSTR pName, LPTSTR pEnvironment, LPTSTR pPathName, LPTSTR pPrintProcessorName ); | winspool.drv | Cette fonction permet d'installer un processeur d'imprimante à partir d'un serveur et d'une liste processeur d'imprimante supporté.
AddPrintProvidorA | BOOL AddPrintProvidorA( LPTSTR pName, DWORD Level, LPBYTE pProvidorInfo ); | winspool.drv | Cette fonction permet d'installer un fournisseur d'imprimante avec la configuration, les données et les fichiers du fournisseur.
AddWordsHWL | int AddWordsHWL( HWL hwl, LPSTR lpsz, UINT uType ); | penwin32.dll | Cette fonction permet d'ajouter des mots dans une liste de mots.
AdjustTokenGroups | BOOL AdjustTokenGroups( HANDLE TokenHandle, BOOL ResetToDefault, PTOKEN_GROUPS NewState, DWORD BufferLength, PTOKEN_GROUPS PreviousState, PDWORD ReturnLength ); | advapi32.dll | Cette fonction permet d'ajuster des groupes dans une liste d'objet à saisir.
AdjustTokenPrivileges | BOOL AdjustTokenPrivileges( HANDLE TokenHandle, BOOL DisableAllPrivileges, PTOKEN_PRIVILEGES NewState, DWORD BufferLength, PTOKEN_PRIVILEGES PreviousState, PDWORD ReturnLength ); | advapi32.dll | Cette fonction permet d'ajuster des prévilèges dans une liste d'objet à saisir.
AdjustWindowRect | BOOL AdjustWindowRect( LPRECT lpRect, DWORD dwStyle, BOOL bMenu ); | user32.dll | Cette fonction permet de calculer l'espace requis par le rectangle d'une fenêtre basé sur un rectangle client.
AdjustWindowRectEx | BOOL AdjustWindowRectEx( LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle ); | user32.dll | Cette fonction permet de calculer l'espace requis par le rectangle d'une fenêtre avec un style étendue basé sur un rectangle client.
AdvancedDocumentPropertiesA | LONG AdvancedDocumentPropertiesA( HWND hWnd, HANDLE hPrinter, LPTSTR pDeviceName, PDEVMODE pDevModeOutput, PDEVMODE pDevModeInput ); | winspool.drv | Cette fonction permet d'afficher la configuration de l'imprimante spécifié dans une boite de dialogue.
AllocateAndInitializeSid | BOOL AllocateAndInitializeSid( PSID_IDENTIFIER_AUTHORITY pIdentifierAuthority, BYTE nSubAuthorityCount, DWORD dwSubAuthority0, DWORD dwSubAuthority1, DWORD dwSubAuthority2, DWORD dwSubAuthority3, DWORD dwSubAuthority4, DWORD dwSubAuthority5, DWORD dwSubAuthority6, DWORD dwSubAuthority7, PSID *pSid ); | advapi32.dll | Cette fonction permet d'alloué ainsi que d'initialiser l'identificateur de sécurité (SID) avec jusqu'à 8 sous-autorités.
AllocateLocallyUniqueId | BOOL AllocateLocallyUniqueId(PLUID Luid); | advapi32.dll | Cette fonction permet d'alloué un identificateur local unique (LUID).
AllocConsole | BOOL AllocConsole(void); | kernel32.dll | Cette fonction permet de créer une nouvelle console.
AngleArc | BOOL AngleArc( HDC hdc, int X, int Y, DWORD dwRadius, FLOAT eStartAngle, FLOAT eSweepAngle ); | gdi32.dll | Cette fonction permet de dessiner une ligne de segment et d'un arc de cercle.
AnimatePalette | BOOL AnimatePalette( HPALETTE hpal, UINT iStartIndex, UINT cEntries, CONST PALETTEENTRY *ppe ); | gdi32.dll | Cette fonction permet de fixer la valeur des palettes de couleur logique.
AnyPopup | BOOL AnyPopup(VOID); | user32.dll | Cette fonction permet d'indiquer s'il existe un moins une fenêtre visible ou invisible sur le bureau de l'écran.
AppendMenuA | BOOL AppendMenuA(HMENU hMenu, UINT uFlags, UINT uIDNewItem, LPCTSTR lpNewItem); | user32.dll | Cette fonction permet d'ajouter un nouvel item au menu courant.
AppendMenuW | BOOL AppendMenuW(HMENU hMenu, UINT uFlags, UINT uIDNewItem, LPCWSTR lpNewItem); | user32.dll | Cette fonction permet d'ajouter un nouvel item au menu courant avec des paramètres de chaine de caractères étendues (Unicode).
Arc | BOOL Arc( HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, int nXStartArc, int nYStartArc, int nXEndArc, int nYEndArc ); | gdi32.dll | Cette fonction permet de dessiner l'arc d'une ellipse.
ArcTo | BOOL ArcTo( HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, int nXRadial1, int nYRadial1, int nXRadial2, int nYRadial2 ); | gdi32.dll | Cette fonction permet de dessiner un arc d'une ellipse.
AreAllAccessesGranted | BOOL AreAllAccessesGranted( DWORD GrantedAccess, DWORD DesiredAccess ); | advapi32.dll | Cette fonction permet d'effectuer la vérification sur un ensemble de requête d'accès garantie.
AreAnyAccessesGranted | BOOL AreAnyAccessesGranted( DWORD GrantedAccess, DWORD DesiredAccess ); | advapi32.dll | Cette fonction permet d'effectuer la vérification sur n'importe quel élément d'un ensemble de requête d'accès garantie.
AreFileApisANSI | BOOL AreFileApisANSI(VOID); | kernel32.dll | Cette fonction permet d'indiquer si les fonctions pour fichier du Win32 utilise un ensemble de police de caractères ANSI ou une page OEM.
ArrangeIconicWindows | UINT ArrangeIconicWindows(HWND hWnd); | user32.dll | Cette fonction permet de minimiser les fenêtres enfants d'une fenêtre parente.
AttachThreadInput | BOOL AttachThreadInput( DWORD idAttach, DWORD idAttachTo, BOOL fAttach ); | user32.dll | Cette fonction permet de relier un processus avec un autre.
auxGetDevCapsA | MMRESULT auxGetDevCaps(UINT uDeviceID, LPAUXCAPS lpCaps, UINT cbCaps); | winmm.dll | Cette fonction permet de demander les informations concernant le système audio auxiliaire.
auxGetNumDevs | UINT auxGetNumDevs(void); | winmm.dll | Cette fonction permet de demander le nombre de système audio auxiliaire reconnu par Windows.
BackupEventLogA | BOOL BackupEventLogA( HANDLE hEventLog, LPCTSTR lpBackupFileName ); | advapi32.dll | Cette fonction permet d'effectuer une sauvegarde du journal de bord dans un fichier de copie de sécurité.
BackupEventLogW | BOOL BackupEventLogW( HANDLE hEventLog, LPCWSTR lpBackupFileName ); | advapi32.dll | Cette fonction permet d'effectuer une sauvegarde du journal de bord dans un fichier de copie de sécurité avec des paramètres de chaine de caractères étendue (Unicode).
BackupRead | BOOL BackupRead( HANDLE hFile, LPBYTE lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, BOOL bAbort, BOOL bProcessSecurity, LPVOID *lpContext ); | kernel32.dll | Cette fonction permet d'effectuer la lecture de flux de données associé avec un fichier ou un répertoire spécifié d'un tampon.
BackupSeek | BOOL BackupSeek( HANDLE hFile, DWORD dwLowBytesToSeek, DWORD dwHighBytesToSeek, LPDWORD lpdwLowByteSeeked, LPDWORD lpdwHighByteSeeked, LPVOID * lpContext ); | kernel32.dll | Cette fonction permet de fixer la position dans le flux de données accessible par les fonctions «BackupRead» et «BackupWrite».
BackupWrite | BOOL BackupWrite( HANDLE hFile, LPBYTE lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, BOOL bAbort, BOOL bProcessSecurity, LPVOID *lpContext ); | kernel32.dll | Cette fonction permet d'effectuer l'écriture de flux de données associé avec un fichier ou un répertoire spécifié d'un tampon.
Beep | bool Beep(UInt32 dwFreq, UInt32 dwDuration); | kernel32.dll | Cette fonction permet d'émettre un signal sonore sur le PC Speaker.
BeginDeferWindowPos | HDWP BeginDeferWindowPos(int nNumWindows); | user32.dll | Cette fonction permet d'allouer de la mémoire sur de multiples position de fenêtres d'une structure de données.
BeginPaint | HDC BeginPaint(HWND hwnd, LPPAINTSTRUCT lpPaint); | user32.dll | Cette fonction permet de préparé les spécifications de la fenêtre pour la peindre et la remplir avec les informations de la structure spécifié.
BeginPath | BOOL BeginPath(HDC hdc); | gdi32.dll | Cette fonction permet d'ouvrir une braquette de chemin pour le périphérique de contexte (DC) spécifié.
BeginUpdateResourceA | HANDLE BeginUpdateResourceA( LPCTSTR pFileName, BOOL bDeleteExistingResources ); | kernel32.dll | Cette fonction permet de retourner l'identificateur Handle qu'utilise une fonction «UpdateResource» pour ajouter, supprimer ou remplacer dans un fichier exécutable.
BeginUpdateResourceW | HANDLE BeginUpdateResourceW(LPCWSTR pFileName,BOOL bDeleteExistingResources); | kernel32.dll | Cette fonction permet de retourner l'identificateur Handle qu'utilise une fonction «UpdateResource» pour ajouter, supprimer ou remplacer dans un fichier exécutable avec des paramètres de chaine de caractères étendue (Unicode).
BitBlt | BOOL BitBlt( HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, HDC hdcSrc, int nXSrc, int nYSrc, DWORD dwRop ); | gdi32.dll | Cette fonction permet d'effectuer la copie d'un Bitmap vers l'écran.
BlockInput | BOOL BlockInput(BOOL fBlockIt); | user32.dll | Cette fonction permet d'arrêter les événements d'entrer comme le clavier, la souris,...
BringWindowToTop | BOOL BringWindowToTop(HWND hWnd); | user32.dll | Cette fonction permet d'activer et de porter, en haut de la pile des fenêtres, la fenêtre spécifié.
BroadcastSystemMessageA | long BroadcastSystemMessageA(DWORD dwFlags, LPDWORD lpdwRecipients, UINT uiMessage, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'envoyer un message vers un récipient spécifié.
BroadcastSystemMessageW | long BroadcastSystemMessageW(DWORD dwFlags, LPDWORD lpdwRecipients, UINT uiMessage, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'envoyer un message vers un récipient spécifié avec des paramètres de chaine de caractères étendues (Unicode).
BuildCommDCBA | BOOL BuildCommDCBA( LPCTSTR lpDef, LPDCB lpDCB ); | kernel32.dll | Cette fonction permet de remplir une structure DCB avec les valeurs spécifié par une chaine de caractères de périphérique de contrôle.
BuildCommDCBW | BOOL BuildCommDCBW(LPCWSTR devid, LPDCB lpdcb); | kernel32.dll | Cette fonction permet de remplir une structure DCB avec les valeurs spécifié par une chaine de caractères de périphérique de contrôle avec des paramètres de chaine de caractères étendue (Unicode).
BuildCommDCBAndTimeoutsA | BOOL BuildCommDCBAndTimeoutsA( LPCTSTR lpDef, LPDCB lpDCB, LPCOMMTIMEOUTS lpCommTimeouts ); | kernel32.dll | Cette fonction permet de convertir la chaine de caractères des définitions d'un périphérique en un bloc de périphérique de contrôle et de placer les codes dans un bloc dans le périphérique de contrôle.
BuildCommDCBAndTimeoutsW | BOOL BuildCommDCBAndTimeoutsW( LPCWSTR devid, LPDCB lpdcb, LPCOMMTIMEOUTS lptimeouts ); | kernel32.dll | Cette fonction permet de convertir la chaine de caractères des définitions d'un périphérique en un bloc de périphérique de contrôle et de placer les codes dans un bloc dans le périphérique de contrôle avec des paramètres de chaine de caractères étendue (Unicode).
CallMsgFilterA | BOOL CallMsgFilterA(LPMSG lpMsg, int nCode); | user32.dll | Cette fonction permet de passer un message à la fonction de filtre de message courant.
CallMsgFilterW | BOOL CallMsgFilterW(LPMSG lpMsg, int nCode); | user32.dll | Cette fonction permet de passer un message à la fonction de filtre de message courant avec des paramètres de chaine de caractères étendues (Unicode).
CallNamedPipeA | BOOL CallNamedPipeA( LPCTSTR lpNamedPipeName, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesRead, DWORD nTimeOut ); | kernel32.dll | Cette fonction permet d'effectuer une connection à l'aide d'un tuyau de type de message et de lire et d'écrire dans se tuyau et d'ensuite fermer le tuyau.
CallNamedPipeW | BOOL CallNamedPipeW( LPCWSTR lpNamedPipeName, PVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, PDWORD lpBytesRead, DWORD nTimeOut ); | kernel32.dll | Cette fonction permet d'effectuer une connection à l'aide d'un tuyau de type de message et de lire et d'écrire dans se tuyau et d'ensuite fermer le tuyau avec des paramètres de chaine de caractères étendue (Unicode).
CallNextHookEx | LRESULT CallNextHookEx(HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet de passer au prochain crochet des informations de crochet dans une chaine de crochet.
CallWindowProcA | LRESULT CallWindowProcA(WNDPROC lpPrevWndFunc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'appeler et de passer des informations de message à la fonction de fenêtre spécifié.
CallWindowProcW | LRESULT CallWindowProcW(WNDPROC func, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'appeler et de passer des informations de message à la fonction de fenêtre spécifié avec des paramètres de chaine de caractères étendues (Unicode).
CancelDC | BOOL CancelDC(HDC hdc); | gdi32.dll | Cette fonction permet d'annuler une opération en attente pour le périphérique de contexte (DC) spécifié.
CancelIo | BOOL CancelIo(HANDLE handle); | kernel32.dll | Cette fonction permet d'annuler une opération d'entrée/sortie initié par un processus léger dans un fichier.
CancelWaitableTimer | BOOL CancelWaitableTimer(HANDLE hTimer); | kernel32.dll | Cette fonction permet de fixer le délai d'attente de l'horloge à un état inactif.
CascadeWindows | WORD CascadeWindows(HWND hwndParent, UINT wHow, CONST RECT * lpRect, UINT cKids, const HWND FAR * lpKids); | user32.dll | Cette fonction permet d'effectuer des cascades de fenêtre spécifié ou de fenêtre enfant dans la fenêtre parent spécifié.
ChangeClipboardChain | BOOL ChangeClipboardChain(HWND hWndRemove, HWND hWndNewNext); | user32.dll | Cette fonction permet d'enlever le premier élément de la chaine du visualisateur de presse-papier et de le remplacer par le deuxième élément spécifié.
ChangeDisplaySettingsA | LONG ChangeDisplaySettingsA(LPDEVMODE lpDevMode, DWORD dwflags); | user32.dll | Cette fonction permet de modifier les paramètres du mode d'affichage graphique
ChangeDisplaySettingsW | LONG ChangeDisplaySettingsW(PDEVMODEW lpDevMode, DWORD dwflags); | user32.dll | Cette fonction permet de modifier les paramètres du mode d'affichage graphique avec des paramètres de chaine de caractères étendues (Unicode).
ChangeDisplaySettingsExA | LONG ChangeDisplaySettingsExA(LPCTSTR lpszDeviceName, DEVMODE *lpDevMode, HWND hwnd, DWORD dwflags, LPVOID lParam); | user32.dll | Cette fonction permet de modifier les paramètres du mode d'affichage graphique pour le périphérique d'affichage spécifié.
ChangeDisplaySettingsExW | LONG ChangeDisplaySettingsExW(LPCWSTR lpszDeviceName, LPDEVMODEW *lpDevMode, HWND hwnd, DWORD dwflags, LPVOID lParam); | user32.dll | Cette fonction permet de modifier les paramètres du mode d'affichage graphique pour le périphérique d'affichage spécifié avec des paramètres de chaine de caractères étendues (Unicode).
ChangeMenuA | int ChangeMenuA(HMENU hMenu, UINT wID, LPCSTR lpszNew, UINT wIDNew, UINT wChange); | user32.dll | Cette fonction permet de modifier un élément du menu spécifié.
ChangeMenuW | int ChangeMenuW(HMENU hMenu, UINT wID, LPCWSTR lpszNew, UINT wIDNew, UINT wChange); | user32.dll | Cette fonction permet de modifier un élément du menu spécifié avec des paramètres de chaine de caractères étendues (Unicode).
CharLowerA | LPTSTR CharLowerA(LPTSTR lpsz); | user32.dll | Cette fonction permet d'effectuer la conversion d'une chaine de caractères spécifié en minuscule.
CharLowerBuffA | DWORD CharLowerBuffA(LPTSTR lpsz, DWORD cchLength); | user32.dll | Cette fonction permet d'effectuer la conversion des caractères majuscules contenu dans un tampon en caractères minuscule.
CharLowerBuffW | DWORD CharLowerBuffW(LPWSTR lpsz, DWORD cchLength); | user32.dll | Cette fonction permet d'effectuer la conversion des caractères majuscules contenu dans un tampon en caractères minuscule avec des paramètres de chaine de caractères étendues (Unicode).
CharLowerW | LPWSTR CharLowerW(LPWSTR lpsz); | user32.dll | Cette fonction permet d'effectuer la conversion d'une chaine de caractères spécifié en minuscule avec des paramètres de chaine de caractères étendues (Unicode).
CharNextA | LPTSTR CharNextA(LPCTSTR lpsz); | user32.dll | Cette fonction permet de retourner un pointeur sur le prochaine caractère contenu dans une chaine de caractères.
CharNextExA | LPSTR CharNextExA(WORD CodePage, LPCSTR lpCurrentChar, DWORD dwFlags); | user32.dll | Cette fonction permet de retourner l'adresse du prochaine caractère contenu dans une chaine de caractères.
CharNextW | LPWSTR CharNextW(LPCWSTR lpsz); | user32.dll | Cette fonction permet de retourner un pointeur sur le prochaine caractère contenu dans une chaine de caractères avec des paramètres de chaine de caractères étendues (Unicode).
CharPrevA | LPTSTR CharPrevA(LPCTSTR lpszStart, LPCTSTR lpszCurrent); | user32.dll | Cette fonction permet de retourner un pointeur sur le caractère précédent contenu dans une chaine de caractères.
CharPrevExA | LPSTR CharPrevExA(WORD CodePage, LPCSTR lpStart, LPCSTR lpCurrentChar, DWORD dwFlags); | user32.dll | Cette fonction permet de retourner l'adresse du caractère précédent contenu dans une chaine de caractères.
CharPrevW | LPWSTR CharPrevW(LPCWSTR lpszStart, LPCWSTR lpszCurrent); | user32.dll | Cette fonction permet de retourner un pointeur sur le caractère précédent contenu dans une chaine de caractères avec des paramètres de chaine de caractères étendues (Unicode).
CharToOemA | BOOL CharToOemA(LPCTSTR lpszSrc, LPSTR lpszDst); | user32.dll | Cette fonction permet d'effectuer la transformation d'une chaine de caractères en ensemble de caractères OEM utilisateur.
CharToOemBuffA | BOOL CharToOemBuffA(LPCTSTR lpszSrc, LPSTR lpszDst, DWORD cchDstLength); | user32.dll | Cette fonction permet d'effectuer la transformation d'un nombre spécifique de caractères dans une chaine de caractères d'un ensemble de caractères OEM utilisateur.
CharToOemBuffW | BOOL CharToOemBuffW(LPCWSTR lpszSrc, LPSTR lpszDst, DWORD cchDstLength); | user32.dll | Cette fonction permet d'effectuer la transformation d'un nombre spécifique de caractères dans une chaine de caractères d'un ensemble de caractères OEM utilisateur avec des paramètres de chaine de caractères étendues (Unicode).
CharToOemW | BOOL CharToOemW(LPCWSTR lpszSrc, LPSTR lpszDst); | user32.dll | Cette fonction permet d'effectuer la transformation d'une chaine de caractères en ensemble de caractères OEM utilisateur avec des paramètres de chaine de caractères étendues (Unicode).
CharUpperA | LPTSTR CharUpperA(LPTSTR lpsz); | user32.dll | Cette fonction permet d'effectuer la conversion d'une chaine de caractères spécifié en majuscule.
CharUpperBuffA | DWORD CharUpperBuffA(LPTSTR lpsz, DWORD cchLength); | user32.dll | Cette fonction permet d'effectuer la conversion des caractères minuscules contenu dans un tampon en caractères majuscule.
CharUpperBuffW | DWORD CharUpperBuffW(LPWSTR lpsz, DWORD cchLength); | user32.dll | Cette fonction permet d'effectuer la conversion des caractères minuscules contenu dans un tampon en caractères majuscule avec des paramètres de chaine de caractères étendues (Unicode).
CharUpperW | LPWSTR CharUpperW(LPWSTR lpsz); | user32.dll | Cette fonction permet d'effectuer la conversion d'une chaine de caractères spécifié en majuscule avec des paramètres de chaine de caractères étendues (Unicode).
CheckColorsInGamut | BOOL CheckColorsInGamut(HDC hdc, LPVOID lpaRGBQuad, LPVOID lpResult, DWORD nCount); | gdi32.dll | Cette fonction permet d'indiquer si les valeurs de couleurs spécifiés sont dans la gamme du périphérique spécifié.
CheckDlgButton | BOOL CheckDlgButton(HWND hDlg, int nIDButton, UINT uCheck); | user32.dll | Cette fonction permet de fixer ou d'enlever une marque de vérification d'un bouton de contrôle ou de changer l'état d'un contrôle d'arbre de bouton.
CheckMenuItem | DWORD CheckMenuItem(HMENU hmenu, UINT uIDCheckItem, UINT uCheck); | user32.dll | Cette fonction permet de fixer ou d'enlever un marque de vérification d'un item de menu dans un menu contextuel.
CheckMenuRadioItem | BOOL CheckMenuRadioItem(HMENU hmenu, UINT idFirst, UINT idLast, UINT idCheck, UINT uFlags); | user32.dll | Cette fonction permet de vérifier l'item de menu spécifié et de fabriquer un item radio.
CheckRadioButton | BOOL CheckRadioButton(HWND hDlg, int nIDFirstButton, int nIDLastButton, int nIDCheckButton); | user32.dll | Cette fonction permet de vérifier ou d'enlever un marque de vérification d'un groupe de bouton radio spécifié.
ChildWindowFromPoint | HWND ChildWindowFromPoint(HWND hWndParent, POINT Point); | user32.dll | Cette fonction permet de demander le propriétaire de la fenêtre enfant à l'aide d'une coordonnée spécifié.
ChildWindowFromPointEx | HWND ChildWindowFromPointEx(HWND hwndParent, POINT pt, UINT uFlags); | user32.dll | Cette fonction permet de demander lequel des fenêtres enfants, si possible, appartient à la fenêtre parent du point spécifié.
ChooseColorA | BOOL ChooseColorA(LPCHOOSECOLOR lpcc); | comdlg32.dll | Cette fonction permet de lancer une boite de dialogue offrant la possibilité à l'utilisateur de choisir une couleur
ChooseFontA | BOOL ChooseFontA(LPCHOOSEFONT lpcf); | comdlg32.dll | Cette fonction permet de lancer une boite de dialogue offrant la possibilité à l'utilisateur de choisir une police de caractères
ChoosePixelFormat | int ChoosePixelFormat(HDC hdc, const PIXELFORMATDESCRIPTOR *ppfd); | gdi32.dll | Cette fonction permet de tenter de faire correspondre à un format de pixel approprié supporté par un périphérique de contexte d'un format de pixel donné spécifié.
Chord | BOOL Chord(HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, int nXRadial1, int nYRadial1, int nXRadial2, int nYRadial2); | gdi32.dll | Cette fonction permet de dessiner la corde de limite à l'aide d'une intersection d'une ellipse avec le centre d'un rectangle et d'une ligne de segment.
ClearCommBreak | BOOL ClearCommBreak(HANDLE hFile); | kernel32.dll | Cette fonction permet de restituer les caractères transmis par un périphérique de communications spécifié et place ligne de transmission dans un état non arrêtable.
ClearCommError | BOOL ClearCommError( HANDLE hFile, LPDWORD lpErrors, LPCOMSTAT lpStat ); | kernel32.dll | Cette fonction permet de demander des informations à propos d'erreur de communications et un rapport d'état actuel sur un périphérique de communication.
ClearEventLogA | BOOL ClearEventLogA( HANDLE hEventLog, LPCTSTR lpBackupFileName ); | advapi32.dll | Cette fonction permet d'effacer le journal de bord spécifié et sauvegarde une copie du journal du bord courant dans un fichier si désiré.
ClearEventLogW | BOOL ClearEventLogW( HANDLE hEventLog, LPCWSTR lpBackupFileName ); | advapi32.dll | Cette fonction permet d'effacer le journal de bord spécifié et sauvegarde une copie du journal du bord courant dans un fichier si désiré avec des paramètres de chaine de caractères étendue (Unicode).
ClientToScreen | BOOL ClientToScreen(HWND hWnd,LPPOINT lpPoint); | user32.dll | Cette fonction permet d'effectuer la transformation des coordonnées logique du client en coordonnées physiquement sur l'écran
ClipCursor | BOOL ClipCursor(CONST RECT * lpRect); | user32.dll | Cette fonction permet de définir une fenêtre limitant le déplacement du pointeur de la souris.
CloseClipboard | BOOL CloseClipboard(VOID); | user32.dll | Cette fonction permet d'indiquer que le traitement avec le presse-papier est terminé et que les autres applications peuvent maintenant avoir accès aux données qu'elle contient.
CloseDesktop | BOOL CloseDesktop(HDESK hDesktop); | user32.dll | Cette fonction permet de fermer ou d'ouvrir le Handle de l'objet de bureau.
CloseEnhMetaFile | HENHMETAFILE CloseEnhMetaFile(HDC hdc); | gdi32.dll | Cette fonction permet de fermer un périphérique de contexte de fichier méta étendue et retourne le Handle identifiant un format de fichier méta étendue.
CloseEventLog | BOOL CloseEventLog(HANDLE hEventLog); | advapi32.dll | Cette fonction permet de fermer le fichier de journal de bord d'événement spécifié
CloseFigure | BOOL CloseFigure(HDC hdc); | gdi32.dll | Cette fonction permet de fermer une figure dans une spécification de chemin.
CloseHandle | BOOL CloseHandle(HANDLE hObject); | kernel32.dll | Cette fonction permet d'effectuer la fermeture d'un Handle.
CloseMetaFile | HMETAFILE CloseMetaFile(HDC hdc); | gdi32.dll | Cette fonction permet de fermer et de créer le fichier de méta pour pouvoir jouer son contenu.
ClosePrinter | BOOL ClosePrinter(HANDLE hPrinter); | winspool.drv | Cette fonction permet d'indiquer que le traitement avec le tampon d'impression de l'imprimante est terminé.
CloseWindow | BOOL CloseWindow(HWND hWnd); | user32.dll | Cette fonction permet de minimiser (sans la détruire) la fenêtre spécifié.
CloseWindowStation | BOOL CloseWindowStation(HWINSTA hWinSta); | user32.dll | Cette fonction permet de fermer un Handle de fenêtre de station ouverte.
CoCreateGuid | HRESULT CoCreateGuid(GUID * pguid); | ole32.dll | Cette fonction permet de créer un identificateur global unique GUID (Global Unique ID) générer sur une base de 128-bits pouvant être utilisé par exemple pour les identificateurs de classe CLSID.
ColorMatchToTarget | BOOL ColorMatchToTarget(HDC hdc, HDC hdcTarget, DWORD uiAction); | gdi32.dll | Cette fonction permet d'activer ou désactiver le prévisualisation du périphérique de contexte spécifié.
CombineRgn | int CombineRgn(HRGN hrgnDest, HRGN hrgnSrc1, HRGN hrgnSrc2, int fnCombineMode); | gdi32.dll | Cette fonction permet de créer une nouvelle région pour combiné deux régions existantes.
CombineTransform | BOOL CombineTransform(LPXFORM lpxformResult, const XFORM *lpxform1, const XFORM *lpxform2); | gdi32.dll | Cette fonction permet d'effectuer la concaténation de double espace mondiale vers une transformation d'espace de page.
CommConfigDialogA | BOOL CommConfigDialogA( LPTSTR lpszName, HWND hWnd, LPCOMMCONFIG lpCC, ); | kernel32.dll | Cette fonction permet d'afficher une configuration de pilote de fournisseur dans une boite de dialogue.
CommConfigDialogW | BOOL CommConfigDialogW( LPCWSTR lpszDevice, HWND hWnd, LPCOMMCONFIG lpCommConfig ); | kernel32.dll | Cette fonction permet d'afficher une configuration de pilote de fournisseur dans une boite de dialogue avec des paramètres de chaine de caractères étendue (Unicode).
CompareFileTime | LONG CompareFileTime( CONST FILETIME *lpFileTime1, CONST FILETIME *lpFileTime2 ); | kernel32.dll | Cette fonction permet de comparer deux temps de fichier de 64-bits.
CompareStringA | int CompareStringA(LCID Locale, DWORD dwCmpFlags, LPCTSTR lpString1, int cchCount1, LPCTSTR lpString2, int cchCount2 ); | kernel32.dll | Cette fonction permet d'effectuer la comparaison de deux chaines de caractères ASCIZ.
CompareStringW | INT CompareStringW( LCID lcid, DWORD style, LPCWSTR str1, INT len1, LPCWSTR str2, INT len2 ); | kernel32.dll | Cette fonction permet d'effectuer la comparaison de deux chaines de caractères Unicode.
ConfigurePortA | BOOL ConfigurePortA(LPTSTR pName,HWND hWnd,LPTSTR pPortName); | winspool.drv | Cette fonction permet de lancer une boite de dialogue offrant la possibilité à l'utilisateur de choisir le port parallèle ou USB d'imprimante
ConnectNamedPipe | BOOL ConnectNamedPipe( HANDLE hNamedPipe, LPOVERLAPPED lpOverlapped ); | kernel32.dll | Cette fonction permet d'activé un nom de tuyau d'un processus serveur en attente pour un processus client de connexion.
ContinueDebugEvent | BOOL ContinueDebugEvent( DWORD dwProcessId, DWORD dwThreadId, DWORD dwContinueStatus ); | kernel32.dll | Cette fonction permet d'activer un débogueur pour qu'il continue un processus précédemment rapporter comme un bogue d'événement.
ConvertDefaultLocale | LCID ConvertDefaultLocale(LCID Locale); | kernel32.dll | Cette fonction permet de convertir une valeur locale spécial par défaut en un identificatuer local courant.
ConvertThreadToFiber | LPVOID ConvertThreadToFiber(LPVOID lpParameter); | kernel32.dll | Cette fonction permet d'effectuer la conversion d'un processus léger vers un fibre.
CopyAcceleratorTableA | int CopyAcceleratorTableA(HACCEL hAccelSrc, LPACCEL lpAccelDst, int cAccelEntries); | user32.dll | Cette fonction permet de copier la table d'accélérateur spécifié.
CopyAcceleratorTableW | int CopyAcceleratorTableW(HACCEL hAccelSrc, LPACCEL lpAccelDst, int cAccelEntries); | user32.dll | Cette fonction permet de copier la table d'accélérateur spécifié avec des paramètres de chaine de caractères étendues (Unicode).
CopyEnhMetaFileA | HENHMETAFILE CopyEnhMetaFileA(HENHMETAFILE hemfSrc, LPCTSTR lpszFile); | gdi32.dll | Cette fonction permet de copier le contenu d'un fichier méta de format étendue vers un fichier spécifié.
CopyEnhMetaFileW | HENHMETAFILE CopyEnhMetaFileW(HENHMETAFILE hmfSrc, LPCWSTR file); | gdi32.dll | Cette fonction permet de copier le contenu d'un fichier méta de format étendue vers un fichier spécifié avec des paramètres de chaine de caractères étendues (Unicode).
CopyFileA | BOOL CopyFileA(LPCTSTR lpExistingFileName,LPCTSTR lpNewFileName,BOOL bFailIfExists); | kernel32.dll | Cette fonction permet d'effectuer la copie d'un fichier d'un emplacement à un autre emplacement
CopyFileExA | BOOL CopyFileExA( LPCSTR lpExistingFileName, LPCSTR lpNewFileName, LPPROGRESS_ROUTINE lpProgressRoutine, LPVOID lpData, LPBOOL pbCancel, DWORD dwCopyFlags ); | kernel32.dll | Cette fonction permet d'effectuer la copie d'un fichier d'un emplacement à un autre emplacement tout en effectuant un processus d'arrière plan indiquant sa progression.
CopyFileExW | BOOL CopyFileExW( LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName, LPPROGRESS_ROUTINE lpProgressRoutine, LPVOID lpData, LPBOOL pbCancel, DWORD dwCopyFlags); | kernel32.dll | Cette fonction permet d'effectuer la copie d'un fichier d'un emplacement à un autre emplacement tout en effectuant un processus d'arrière plan indiquant sa progression avec des paramètres de chaine de caractères étendue (Unicode).
CopyFileW | BOOL CopyFileW( WCHAR *from, WCHAR *to, BOOL bFailIfExists); | kernel32.dll | Cette fonction permet d'effectuer la copie d'un fichier d'un emplacement à un autre emplacement avec des paramètres de chaine de caractères étendue (Unicode).
CopyIcon | HICON CopyIcon(HICON hIcon); | user32.dll | Cette fonction permet de copier un icône spécifié d'un autre module vers le module courant.
CopyImage | HANDLE CopyImage(HANDLE hImage, UINT uType, int cxDesired, int cyDesired, UINT fuFlags); | user32.dll | Cette fonction permet de créer une nouvelle image (icône, image ou Bitmap) et de copier les attributs de l'image donnée dans une nouvelle image.
CopyMetaFileA | HMETAFILE CopyMetaFileA(HMETAFILE hmfSrc, LPCTSTR lpszFile); | gdi32.dll | Cette fonction permet de copier le contenu d'un format de méta fichier Windows vers un fichier spécifié.
CopyMetaFileW | HMETAFILE CopyMetaFileW(HMETAFILE hSrcMetaFile, LPCWSTR lpFilename); | gdi32.dll | Cette fonction permet de copier le contenu d'un format de méta fichier Windows vers un fichier spécifié avec des paramètres de chaine de caractères étendues (Unicode).
CopyRect | BOOL CopyRect(LPRECT lprcDst, CONST RECT *lprcSrc); | user32.dll | Cette fonction permet d'effectuer la copie des coordonnées d'un rectangle dans un autre rectangle.
CopySid | BOOL CopySid( DWORD nDestinationSidLength, PSID pDestinationSid, PSID pSourceSid ); | advapi32.dll | Cette fonction permet de copier l'identificateur de sécurité (SID) dans un tampon spécifié.
CountClipboardFormats | int CountClipboardFormats(VOID); | user32.dll | Cette fonction permet de demander le nombre de différentes données de formats courant que contient le presse-papier.
CreateAcceleratorTableA | HACCEL CreateAcceleratorTableA(LPACCEL lpaccl, int cEntries); | user32.dll | Cette fonction permet de créer une table d'accélérateur.
CreateAcceleratorTableW | HACCEL CreateAcceleratorTableW(LPACCEL lpaccl, int cEntries); | user32.dll | Cette fonction permet de créer une table d'accélérateur avec des paramètres de chaine de caractères étendues (Unicode).
CreateBitmap | HBITMAP CreateBitmap(int nWidth, int nHeight, UINT cPlanes, UINT cBitsPerPel, const void *lpvBits); | gdi32.dll | Cette fonction permet de créer un périphérique de Bitmap en mémoire avec la largeur, hauteur et la palette de bits spécifié.
CreateBitmapIndirect | HBITMAP CreateBitmapIndirect(CONST BITMAP *lpbm); | gdi32.dll | Cette fonction permet de créer un Bitmap avec la largeur, la hauteur et la palette de bits spécifié dans une structure BITMAP.
CreateBrushIndirect | HBRUSH CreateBrushIndirect(CONST LOGBRUSH * lplb); | gdi32.dll | Cette fonction permet de créer une brosse avec le style, la couleur et la palette spécifié dans la structure «LOGBRUSH».
CreateCaret | BOOL CreateCaret(HWND hWnd, HBITMAP hBitmap, int nWidth, int nHeight); | user32.dll | Cette fonction permet de créer un nouveau signe pour le curseur texte et associe le propriétaire à la fenêtre spécifié.
CreateColorSpaceA | HCOLORSPACE CreateColorSpaceA(LPLOGCOLORSPACE lpLogColorSpace); | gdi32.dll | Cette fonction permet de créer un espace de couleur logique.
CreateColorSpaceW | HCOLORSPACE CreateColorSpaceW(LPLOGCOLORSPACEW lpLogColorSpace); | gdi32.dll | Cette fonction permet de créer un espace de couleur logique avec des paramètres de chaine de caractères étendues (Unicode).
CreateCompatibleBitmap | HBITMAP CreateCompatibleBitmap(HDC hdc, int nWidth, int nHeight); | gdi32.dll | Cette fonction permet de créer un Bitmap compatible avec le périphérique spécifié.
CreateCompatibleDC | HDC CreateCompatibleDC(HDC hdc); | gdi32.dll | Cette fonction permet de créer un contexte de périphérique mémoire compatible avec le périphérique spécifié.
CreateConsoleScreenBuffer | HANDLE CreateConsoleScreenBuffer( DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwFlags, LPVOID lpScreenBufferData ); | kernel32.dll | Cette fonction permet de créer un tampon de console écran et de retourner un identificateur Handle associée à celui-ci.
CreateCursor | HCURSOR CreateCursor(HINSTANCE hInst, int xHotSpot, int yHotSpot, int nWidth, int nHeight, CONST VOID *pvANDPlane, CONST VOID *pvXORPlane ); | user32.dll | Cette fonction permet d'effectuer la création d'une nouvelle image de pointeur de souris
CreateDCA | HDC CreateDCA( LPCTSTR lpszDriver, LPCTSTR lpszDevice, LPCTSTR lpszOutput, CONST DEVMODE *lpInitData ); | gdi32.dll | Cette fonction permet d'effectuer la création d'un objet de contexte (DC)
CreateDCW | HDC CreateDCW(LPCWSTR lpszDriver, LPCWSTR lpszDevice,LPCWSTR lpszOutput,const DEVMODEW * lpInitData); | gdi32.dll | Cette fonction permet d'effectuer la création d'un objet de contexte (DC) avec des paramètres de chaines de caractères étendues (Unicode).
CreateDesktopA | HDESK CreateDesktopA(LPCTSTR lpszDesktop, LPCTSTR lpszDevice, LPDEVMODE pDevMode, DWORD dwFlags, DWORD dwDesiredAccess, LPSECURITY_ATTRIBUTES lpsa); | user32.dll | Cette fonction permet de créer un nouveau bureau dans une fenêtre de station associé avec le processus appelant.
CreateDesktopW | HDESK CreateDesktopW(LPCWSTR lpszDesktop, LPCWSTR lpszDevice, LPDEVMODEW pDevMode, DWORD dwFlags, DWORD dwDesiredAccess, LPSECURITY_ATTRIBUTES lpsa); | user32.dll | Cette fonction permet de créer un nouveau bureau dans une fenêtre de station associé avec le processus appelant avec des paramètres de chaine de caractères étendues (Unicode).
CreateDialogIndirectParamA | HWND CreateDialogIndirectParamA(HINSTANCE hInstance, LPCDLGTEMPLATE lpTemplate, HWND hWndParent, DLGPROC lpDialogFunc, LPARAM lParamInit); | user32.dll | Cette fonction permet de créer un modèle de boite de dialogue pour un gabarit de boite de dialogue en mémoire et après son affichage, la fonction passe une valeur d'application à la procédure de boite de dialogue avec les paramètres spécifié du message «WM_INITDIALOG».
CreateDialogIndirectParamW | HWND CreateDialogIndirectParamW(HINSTANCE hInstance, LPCDLGTEMPLATE lpTemplate, HWND hWndParent, DLGPROC lpDialogFunc, LPARAM lParamInit); | user32.dll | Cette fonction permet de créer un modèle de boite de dialogue pour un gabarit de boite de dialogue en mémoire et après son affichage, la fonction passe une valeur d'application à la procédure de boite de dialogue avec les paramètres spécifié du message «WM_INITDIALOG» avec des paramètres de chaine de caractères étendues (Unicode).
CreateDialogParamA | HWND CreateDialogParamA(HINSTANCE hInstance, LPCTSTR lpTemplateName, HWND hWndParent, DLGPROC lpDialogFunc, LPARAM dwInitParam); | user32.dll | Cette fonction permet de créer un modèle de boite de dialogue pour une ressource de gabarit de boite de dialogue et après son affichage, la fonction passe une valeur d'application à la procédure de boite de dialogue avec les paramètres spécifié du message «WM_INITDIALOG».
CreateDialogParamW | HWND CreateDialogParamW(HINSTANCE hInstance, LPCWSTR lpTemplateName, HWND hWndParent, DLGPROC lpDialogFunc, LPARAM dwInitParam); | user32.dll | Cette fonction permet de créer un modèle de boite de dialogue pour une ressource de gabarit de boite de dialogue et après son affichage, la fonction passe une valeur d'application à la procédure de boite de dialogue avec les paramètres spécifié du message «WM_INITDIALOG» avec des paramètres de chaine de caractères étendues (Unicode).
CreateDIBPatternBrush | HBRUSH CreateDIBPatternBrush(HGLOBAL hglbDIBPacked, UINT fuColorSpec); | gdi32.dll | Cette fonction permet de créer une brosse avec la palette spécifié par un périphérique de Bitmap indépendant (DIB).
CreateDIBPatternBrushPt | HBRUSH CreateDIBPatternBrushPt(CONST VOID * lpPackedDIB, UINT iUsage); | gdi32.dll | Cette fonction permet de créer une brosse logique avec la palette spécifié par un périphérique de Bitmap indépendant (DIB).
CreateDIBSection | HBITMAP CreateDIBSection(HDC hdc, CONST BITMAPINFO *pbmi, UINT iUsage, VOID **ppvBits, HANDLE hSection, DWORD dwOffset); | gdi32.dll | Cette fonction permet de créer un Bitmap de périphérique indépendant (DIB) avec les applications pouvant y être écrit directement.
CreateDIBitmap | HBITMAP CreateDIBitmap(HDC hdc, CONST BITMAPINFOHEADER * lpbmih, DWORD fdwInit, CONST VOID *lpbInit, CONST BITMAPINFO * lpbmi, UINT fuUsage); | gdi32.dll | Cette fonction permet de créer un périphérique mémoire spécifié pour un périphérique de Bitmap indépendant (DIB) spécifié et un ensemble de bits optionnel pour le Bitmap.
CreateDirectoryA | BOOL CreateDirectoryA( LPCTSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes ); | kernel32.dll | Cette fonction permet d'effectuer la création d'un répertoire
CreateDirectoryW | BOOL CreateDirectoryW(LPCWSTR lpPathName,LPSECURITY_ATTRIBUTES lpSecurityAttributes); | kernel32.dll | Cette fonction permet d'effectuer la création d'un répertoire avec des paramètres de chaine de caractères étendue (Unicode).
CreateDirectoryExA | BOOL CreateDirectoryExA( LPCTSTR lpTemplateDirectory, LPCTSTR lpNewDirectory, LPSECURITY_ATTRIBUTES lpSecurityAttributes ); | kernel32.dll | Cette fonction permet d'effectuer la création d'un répertoire étendue
CreateDirectoryExW | BOOL CreateDirectoryExW( LPCWSTR lpTemplateDirectory, LPCWSTR lpNewDirectory, LPSECURITY_ATTRIBUTES lpSecurityAttributes ); | kernel32.dll | Cette fonction permet d'effectuer la création d'un répertoire étendue avec des paramètres de chaine de caractères étendue (Unicode).
CreateDirectoryTransactedA | BOOL CreateDirectoryTransactedA( LPCTSTR lpTemplateDirectory, LPCTSTR lpNewDirectory, LPSECURITY_ATTRIBUTES lpSecurityAttributes, HANDLE hTransaction ); | kernel32.dll | Cette fonction permet d'effectuer la création d'un répertoire dans une opération de transaction
CreateDirectoryTransactedW | BOOL CreateDirectoryTransactedW( LPCTWSTR lpTemplateDirectory, LPCTWSTR lpNewDirectory, LPSECURITY_ATTRIBUTES lpSecurityAttributes, HANDLE hTransaction ); | kernel32.dll | Cette fonction permet d'effectuer la création d'un répertoire dans une opération de transaction avec des paramètres de chaine de caractères étendue (Unicode).
CreateDiscardableBitmap | HBITMAP CreateDiscardableBitmap(HDC hdc, int nWidth, int nHeight); | gdi32.dll | Cette fonction permet de créer un Bitmap compatible avec un périphérique spécifié.
CreateEllipticRgn | HRGN CreateEllipticRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect); | gdi32.dll | Cette fonction permet de créer une région elliptique.
CreateEllipticRgnIndirect | HRGN CreateEllipticRgnIndirect(CONST RECT *lprc); | gdi32.dll | Cette fonction permet de créer une région elliptique à l'aide d'une structure de données.
CreateEnhMetaFileA | HDC CreateEnhMetaFileA(HDC hdcRef, LPCTSTR lpFilename, CONST RECT * lpRect, LPCTSTR lpDescription); | gdi32.dll | Cette fonction permet de créer un contexte de périphérique pour un méta fichier de format étendue.
CreateEnhMetaFileW | HDC CreateEnhMetaFileW(HDC hdcRef, LPCWSTR lpFilename, const RECT *lpRect, LPCWSTR lpDescription); | gdi32.dll | Cette fonction permet de créer un contexte de périphérique pour un méta fichier de format étendue avec des paramètres de chaine de caractères étendues (Unicode).
CreateEventA | HANDLE CreateEventA( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName ); | kernel32.dll | Cette fonction permet de créer un objet d'événement nommé ou non-nommé.
CreateEventW | HANDLE CreateEventW( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCWSTR lpName ); | kernel32.dll | Cette fonction permet de créer un objet d'événement nommé ou non-nommé avec des paramètres de chaine de caractères étendue (Unicode).
CreateFiber | LPVOID CreateFiber( SIZE_T dwStackSize, LPFIBER_START_ROUTINE lpStartAddress, LPVOID lpParameter ); | kernel32.dll | Cette fonction permet d'allouer un objet de fibre, en lui attribuant une pile, et en fixant le début de l'exécutant à l'adresse de début spécifié.
CreateFileA | HANDLE CreateFileA( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDistribution, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile ); | kernel32.dll | Cette fonction permet d'effectuer la création ou l'ouverture d'un fichier ou d'un périphérique.
CreateFileW | HANDLE CreateFileW( LPCWSTR filename, DWORD access, DWORD sharing, LPSECURITY_ATTRIBUTES sa, DWORD creation, DWORD attributes, HANDLE template ); | kernel32.dll | Cette fonction permet d'effectuer la création ou l'ouverture d'un fichier ou d'un périphérique avec des paramètres de chaine de caractères étendue (Unicode).
CreateFileMappingA | HANDLE CreateFileMappingA( HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCTSTR lpName ); | kernel32.dll | Cette fonction permet de créer un objet mapping, nommé ou non-nommé, pour un fichier spécifié.
CreateFileMappingW | HANDLE CreateFileMappingW( HANDLE hFile, LPSECURITY_ATTRIBUTES sa, DWORD protect, DWORD size_high, DWORD size_low, LPCWSTR name ); | kernel32.dll | Cette fonction permet de créer un objet mapping, nommé ou non-nommé, pour un fichier spécifié avec des paramètres de chaine de caractères étendue (Unicode).
CreateFontA | HFONT CreateFontA( int nHeight, int nWidth, int nEscapement, int nOrientation, int fnWeight, DWORD fdwItalic, DWORD fdwUnderline, DWORD fdwStrikeOut, DWORD fdwCharSet, DWORD fdwOutputPrecision, DWORD fdwClipPrecision, DWORD fdwQuality, DWORD fdwPitchAndFamily, LPCTSTR lpszFace ); | gdi32.dll | Cette fonction permet d'effectuer la création d'une police de caractères logique avec les caractéristiques spécifiés.
CreateFontIndirectA | HFONT CreateFontIndirectA(CONST LOGFONT *lplf); | gdi32.dll | Cette fonction permet de créer une police de caractères logique avec les caractéristiques spécifié dans la structure spécifié.
CreateFontIndirectW | HFONT CreateFontIndirectW(CONST LOGFONTW *lplf); | gdi32.dll | Cette fonction permet de créer une police de caractères logique avec les caractéristiques spécifié dans la structure spécifié avec les paramètres de chaines de caractères étendues (Unicode).
CreateFontW | HFONT CreateFontW( int nHeight, int nWidth, int nEscapement, int nOrientation, int fnWeight, DWORD fdwItalic, DWORD fdwUnderline, DWORD fdwStrikeOut, DWORD fdwCharSet, DWORD fdwOutputPrecision, DWORD fdwClipPrecision, DWORD fdwQuality, DWORD fdwPitchAndFamily, LPCWSTR lpszFace ); | gdi32.dll | Cette fonction permet d'effectuer la création d'une police de caractères logique avec les caractéristiques spécifiés avec les paramètres de chaines de caractères étendues (Unicode).
CreateHalftonePalette | HPALETTE CreateHalftonePalette(HDC hdc); | gdi32.dll | Cette fonction permet de créer une demi-teinte d'une palette pour le périphérique de contexte spécifié.
CreateHatchBrush | HBRUSH CreateHatchBrush(int fnStyle, COLORREF clrref); | gdi32.dll | Cette fonction permet de créer une brosse logique spécifiant un motif de hachure et de couleur.
CreateICA | HDC CreateICA( LPCTSTR lpszDriver, LPCTSTR lpszDevice, LPCTSTR lpszOutput, CONST DEVMODE * lpdvmInit ); | gdi32.dll | Cette fonction permet de créer une information de contexte pour un périphérique spécifié.
CreateICW | HDC CreateICW(LPCWSTR lpszDriver, LPCWSTR lpszDevice, LPCWSTR lpszOutput, CONST DEVMODEW * lpdvmInit); | gdi32.dll | Cette fonction permet de créer une information de contexte pour un périphérique spécifié avec des paramètres de chaines de caractères étendues (Unicode).
CreateIcon | HICON CreateIcon( HINSTANCE hInstance, int nWidth, int nHeight, BYTE cPlanes, BYTE cBitsPixel, CONST BYTE *lpbANDbits, CONST BYTE *lpbXORbits ); | user32.dll | Cette fonction permet d'effectuer la création d'une icône
CreateIconFromResource | HICON CreateIconFromResource(PBYTE presbits, DWORD dwResSize, BOOL fIcon, DWORD dwVer); | user32.dll | Cette fonction permet d'effectuer la création d'un icône ou d'un curseur à partir de bits de ressources décrit dans l'icône.
CreateIconFromResourceEx | HICON CreateIconFromResourceEx(PBYTE pbIconBits, DWORD cbIconBits, BOOL fIcon, DWORD dwVersion, int cxDesired, int cyDesired, UINT uFlags); | user32.dll | Cette fonction permet d'effectuer la création d'un icône ou d'un curseur à partir de bits de ressources décrit dans l'icône avec des options complémentaires.
CreateIconIndirect | HICON CreateIconIndirect(PICONINFO piconinfo); | user32.dll | Cette fonction permet d'effectuer la création d'un icône ou d'un curseur à partir d'une structure de données.
CreateMailslotA | HANDLE CreateMailslotA( LPCTSTR lpName, DWORD nMaxMessageSize, DWORD lReadTimeout, LPSECURITY_ATTRIBUTES lpSecurityAttributes ); | kernel32.dll | Cette fonction permet de créer une slot de courriel avec un nom spécifié et retourne un identificateur Handle associé à celle-ci.
CreateMailslotW | HANDLE CreateMailslotW( LPCWSTR lpName, DWORD nMaxMessageSize, DWORD lReadTimeout, LPSECURITY_ATTRIBUTES sa ); | kernel32.dll | Cette fonction permet de créer une slot de courriel avec un nom spécifié et retourne un identificateur Handle associé à celle-ci avec des paramètres de chaine de caractères étendue (Unicode).
CreateMDIWindowA | HWND CreateMDIWindowA(LPTSTR lpClassName, LPTSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HINSTANCE hInstance, LPARAM lParam); | user32.dll | Cette fonction permet d'effectuer la création de fenêtre enfant MDI (Interface de mutiple document).
CreateMDIWindowW | HWND CreateMDIWindowW(LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HINSTANCE hInstance, LPARAM lParam); | user32.dll | Cette fonction permet d'effectuer la création de fenêtre enfant MDI (Interface de mutiple document) avec des paramètres de chaine de caractères étendues (Unicode).
CreateMenu | HMENU CreateMenu(VOID); | user32.dll | Cette fonction permet d'effectuer la création d'un menu.
CreateMetaFileA | HDC CreateMetaFileA(LPCTSTR lpszFile); | gdi32.dll | Cette fonction permet de créer un périphérique de contexte pour un format de fichier méta Windows.
CreateMetaFileW | HDC CreateMetaFileW(LPCWSTR filename); | gdi32.dll | Cette fonction permet de créer un périphérique de contexte pour un format de fichier méta Windows avec des paramètres de chaines de caractères étendues (Unicode).
CreateMutexA | HANDLE CreateMutexA( LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName ); | kernel32.dll | Cette fonction permet de créer un objet Mutex nommé ou non-nommé.
CreateMutexW | HANDLE CreateMutexW( LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCWSTR lpName ); | kernel32.dll | Cette fonction permet de créer un objet Mutex nommé ou non-nommé avec des paramètres de chaine de caractères étendue (Unicode).
CreateNamedPipeA | HANDLE CreateNamedPipeA( LPCTSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut, LPSECURITY_ATTRIBUTES lpSecurityAttributes ); | kernel32.dll | Cette fonction permet de créer un tuyau avec un instance de nom spécifié et de retourner un identificateur Handle associé à celle-ci.
CreateNamedPipeW | HANDLE CreateNamedPipeW( LPCWSTR lpName, DWORD dwOpenMode, DWORD nMaxInstances, DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut, DWORD nDefaultTimeOut, LPSECURITY_ATTRIBUTES lpSecurityAttributes ); | kernel32.dll | Cette fonction permet de créer un tuyau avec un instance de nom spécifié et de retourner un identificateur Handle associé à celle-ci avec des paramètres de chaine de caractères étendue (Unicode).
CreatePalette | HPALETTE CreatePalette(CONST LOGPALETTE *lplgpl); | gdi32.dll | Cette fonction permet d'effectuer la création d'une palette logique.
CreatePatternBrush | HBRUSH CreatePatternBrush(HBITMAP hbmp); | gdi32.dll | Cette fonction permet d'effectuer la création d'une brosse logique avec la palette Bitmap spécifié.
CreatePen | HPEN CreatePen(int fnPenStyle, int nWidth, COLORREF crColor); | gdi32.dll | Cette fonction permet d'effectuer la création d'un crayon logique avec le style, la largeur et la couleur spécifié.
CreatePenIndirect | HPEN CreatePenIndirect(CONST LOGPEN * lplgpn); | gdi32.dll | Cette fonction permet d'effectuer la création d'un crayon logique avec le style, la largeur et la couleur à l'aide la structure de données spécifié.
CreatePipe | BOOL CreatePipe( PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize ); | kernel32.dll | Cette fonction permet de créer un tuyau anonyme et de retourner un identificateur Handle attribué en lecture et écriture pour celle-ci.
CreatePolyPolygonRgn | HRGN CreatePolyPolygonRgn(CONST POINT *lppt, CONST INT *lpPolyCounts, int nCount, int fnPolyFillMode); | gdi32.dll | Cette fonction permet d'effectuer la création d'une région contenant une série de fermeture de polygone.
CreatePolygonRgn | HRGN CreatePolygonRgn(CONST POINT *lppt, int cPoints, int fnPolyFillMode); | gdi32.dll | Cette fonction permet d'effectuer la création d'une région en forme de polygone.
CreatePopupMenu | HMENU CreatePopupMenu(VOID) | user32.dll | Cette fonction permet d'effectuer la création d'un menu contextuel
CreatePrivateObjectSecurity | BOOL CreatePrivateObjectSecurity( PSECURITY_DESCRIPTOR ParentDescriptor, PSECURITY_DESCRIPTOR CreatorDescriptor, PSECURITY_DESCRIPTOR * NewDescriptor, BOOL IsDirectoryObject, HANDLE Token, PGENERIC_MAPPING GenericMapping ); | advapi32.dll | Cette fonction permet d'alloué et d'initialiser un descripteur de sécurité relatif à l'objet pour un un serveur d'objet protégé.
CreateProcessA | BOOL CreateProcessA( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ); | kernel32.dll | Cette fonction permet de créer un nouveau processus et de le fixer comme processus léger primaire.
CreateProcessW | BOOL CreateProcessW( LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, PVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ); | kernel32.dll | Cette fonction permet de créer un nouveau processus et de le fixer comme processus léger primaire avec des paramètres de chaine de caractères étendue (Unicode).
CreateProcessAsUserA | BOOL CreateProcessAsUserA( HANDLE hToken LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ); | advapi32.dll | Cette fonction permet de créer un nouveau processus et de le mettre comme processus primaire.
CreateProcessAsUserW | BOOL CreateProcessAsUserW( HANDLE hToken LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPWSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ); | advapi32.dll | Cette fonction permet de créer un nouveau processus et de le mettre comme processus primaire avec des paramètres de chaine de caractères étendue (Unicode).
CreateRectRgn | HRGN CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect); | gdi32.dll | Cette fonction permet d'effectuer la création d'une région rectangulaire.
CreateRectRgnIndirect | HRGN CreateRectRgnIndirect(CONST RECT *lprc); | gdi32.dll | Cette fonction permet d'effectuer la création d'une région rectangulaire à l'aide d'une structure de données.
CreateRemoteThread | HANDLE CreateRemoteThread( HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ); | kernel32.dll | Cette fonction permet de créer un processus léger exécuté dans l'espace d'une adresse d'un autre processus.
CreateRoundRectRgn | HRGN CreateRoundRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, int nWidthEllipse, int nHeightEllipse); | gdi32.dll | Cette fonction permet d'effectuer la création d'une région rectangulaire avec les coins arrondies.
CreateScalableFontResourceA | BOOL CreateScalableFontResourceA(DWORD fdwHidden, LPCTSTR lpszFontRes, LPCTSTR lpszFontFile, LPCTSTR lpszCurrentPath); | gdi32.dll | Cette fonction permet de créer un fichier de ressource de police de caractères avec une police de caractères vectorielle.
CreateScalableFontResourceW | BOOL CreateScalableFontResourceW(DWORD fdwHidden, LPCWSTR lpszFontRes, LPCWSTR lpszFontFile, LPCWSTR lpszCurrentPath); | gdi32.dll | Cette fonction permet de créer un fichier de ressource de police de caractères avec une police de caractères vectorielle avec des paramètres de chaine de caractères étendue (Unicode).
CreateSemaphoreA | HANDLE CreateSemaphoreA( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName ); | kernel32.dll | Cette fonction permet de créer un objet de sémaphore nommé ou non-nommé.
CreateSemaphoreW | HANDLE CreateSemaphoreW( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCWSTR lpName ); | kernel32.dll | Cette fonction permet de créer un objet de sémaphore nommé ou non-nommé avec des paramètres de chaine de caractères étendue (Unicode).
CreateSolidBrush | HBRUSH CreateSolidBrush(COLORREF crColor); | gdi32.dll | Cette fonction permet d'effectuer la création d'une brosse logique avec la couleur et le motif solide spécifié.
CreateStatusWindowA | HWND CreateStatusWindowA( LONG style, LPCTSTR lpszText, HWND hwndParent, UINT wID ); | comctl32.dll | Cette fonction permet d'effectuer la création d'une barre d'état dans le bas d'une fenêtre d'application
CreateTapePartition | DWORD CreateTapePartition( HANDLE hDevice, DWORD dwPartitionMethod, DWORD dwCount, DWORD dwSize ); | kernel32.dll | Cette fonction permet de reformatté une unité de sauvegarde de bande magnétique (Tape Backup).
CreateToolhelp32Snapshot | HANDLE CreateToolhelp32Snapshot(DWORD dwFlags,DWORD th32ProcessID); | kernel32.dll | Cette fonction permet d'effectuer une capture de l'écran sous forme d'un Bitmap
CreateThread | HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ); | kernel32.dll | Cette fonction permet de créer un processus pour exécuter un processus appelant dans un espace d'adressage.
CreateWaitableTimerA | HANDLE CreateWaitableTimerA( LPSECURITY_ATTRIBUTES lpTimerAttributes, BOOL bManualReset, LPCSTR lpTimerName ); | kernel32.dll | Cette fonction permet d'effectuer la création d'une horloge de délai d'attente.
CreateWaitableTimerW | HANDLE CreateWaitableTimerW( LPSECURITY_ATTRIBUTES lpTimerAttributes, BOOL bManualReset, LPCWSTR lpTimerName ); | kernel32.dll | Cette fonction permet d'effectuer la création d'une horloge de délai d'attente avec des paramètres de chaine de caractères étendue (Unicode).
CreateWindowExA | HWND CreateWindowExA( DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam ); | user32.dll | Cette fonction permet d'effectuer la création d'une sous-fenêtre (PopUp).
CreateWindowExW | HWND CreateWindowExW(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam); | user32.dll | Cette fonction permet d'effectuer la création d'une sous-fenêtre (PopUp) avec des paramètres de chaine de caractères étendues (Unicode).
CreateWindowStationA | HWINSTA CreateWindowStationA(LPTSTR lpwinsta, DWORD dwReserved, DWORD dwDesiredAccess, LPSECURITY_ATTRIBUTES lpsa); | user32.dll | Cette fonction permet d'effectuer la création d'un objet de fenêtre de station.
CreateWindowStationW | HWINSTA CreateWindowStationW(LPCWSTR lpwinsta, DWORD dwReserved, DWORD dwDesiredAccess, LPSECURITY_ATTRIBUTES lpsa); | user32.dll | Cette fonction permet d'effectuer la création d'un objet de fenêtre de station avec des paramètres de chaine de caractères étendues (Unicode).
DdeSetQualityOfService | BOOL DdeSetQualityOfService(HWND hwndClient, const SECURITY_QUALITY_OF_SERVICE *pqosNew, PSECURITY_QUALITY_OF_SERVICE pqosPrev); | user32.dll | Cette fonction permet de spécifier le service de qualité (QoS) des applications d'échanges de données dynamique brute (DDE) désiré pour de future initialisation de conversion DDE.
DebugActiveProcess | BOOL DebugActiveProcess( DWORD dwProcessId ); | kernel32.dll | Cette fonction permet d'allouer une débogueur à attaché avec un processus actif
DebugBreak | VOID DebugBreak(VOID); | kernel32.dll | Cette fonction permet de provoqué une exception sur un point d'arrêt dans le processus courant.
DefDlgProcA | LRESULT DefDlgProcA(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'effectuer le traitement de processus de fenêtre de message par défaut.
DefDlgProcW | LRESULT DefDlgProcW(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'effectuer le traitement de processus de fenêtre de message par défaut avec des paramètres de chaine de caractères étendues (Unicode).
DeferWindowPos | HDWP DeferWindowPos(HDWP hWinPosInfo, HWND hWnd, HWND hWndInsertAfter, int x, int y, int cx, int cy, UINT uFlags); | user32.dll | Cette fonction permet de mettre à jour la structure de positionnement d'une fenêtre.
DefFrameProcA | LRESULT DefFrameProcA(HWND hWnd, HWND hWndMDIClient, UINT uMsg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'effectuer le processus de message par défaut du cadre d'une fenêtre MDI.
DefFrameProcW | LRESULT DefFrameProcW(HWND hWnd, HWND hWndMDIClient, UINT uMsg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'effectuer le processus de message par défaut du cadre d'une fenêtre MDI avec des paramètres de chaine de caractères étendues (Unicode).
DefineDosDeviceA | BOOL DefineDosDeviceA( DWORD dwFlags, LPCTSTR lpDeviceName, LPCTSTR lpTargetPath ); | kernel32.dll | Cette fonction permet de définir, redéfinir ou supprimer un nom de périphérique DOS.
DefineDosDeviceW | BOOL DefineDosDeviceW( DWORD dwFlags, LPCWSTR lpDeviceName, LPCWSTR lpTargetPath ); | kernel32.dll | Cette fonction permet de définir, redéfinir ou supprimer un nom de périphérique DOS avec des paramètres de chaine de caractères étendue (Unicode).
DefMDIChildProcA | LRESULT DefMDIChildProcA(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet de définir le processus de message de la fenêtre enfant MDI.
DefMDIChildProcW | LRESULT DefMDIChildProcW(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet de définir le processus de message de la fenêtre enfant MDI avec des paramètres de chaine de caractères étendues (Unicode).
DefWindowProcA | LRESULT DefWindowProcA(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet de créer un événement produit par défaut lorsqu'une action est effectuer dans la fenêtre.
DefWindowProcW | LRESULT DefWindowProcW(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet de créer un événement produit par défaut lorsqu'une action est effectué dans la fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
DeleteAce | BOOL DeleteAce( PACL pAcl, DWORD dwAceIndex ); | advapi32.dll | Cette fonction permet d'effacer une entrée de contrôle d'accès (ACE) d'une liste de contrôle d'accès (ACL).
DeleteAtom | ATOM DeleteAtom( ATOM nAtom ); | kernel32.dll | Cette fonction permet de décrémenter le compteur de référence de chaine de caractères d'Atom local.
DeleteColorSpace | BOOL DeleteColorSpace(HCOLORSPACE hColorSpace); | gdi32.dll | Cette fonction permet de supprimer l'espace de couleur spécifié et libérer tous les ressources internes associé avec celui-ci.
DeleteCriticalSection | VOID DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection ); | kernel32.dll | Cette fonction permet de terminer et restituer tous les ressources associé à l'objet de section critique.
DeleteDC | BOOL DeleteDC(HDC hdc); | gdi32.dll | Cette fonction permet d'effectuer la suppression d'un périphérique de contexte (DC).
DeleteEnhMetaFile | BOOL DeleteEnhMetaFile(HENHMETAFILE hemf); | gdi32.dll | Cette fonction permet de supprimer le format étendue du fichier méta ou le Handle du fichier méta de format étendue.
DeleteFiber | VOID DeleteFiber( LPVOID lpFiber ); | kernel32.dll | Cette fonction permet de supprimer une fibre existante.
DeleteFileA | BOOL DeleteFileA( LPCTSTR lpFileName ); | kernel32.dll | Cette fonction permet de supprimer un fichier existant.
DeleteFileW | BOOL DeleteFileW( LPCWSTR path ); | kernel32.dll | Cette fonction permet de supprimer un fichier existant avec des paramètres de chaine de caractères étendue (Unicode).
DeleteMenu | BOOL DeleteMenu(HMENU hMenu,UINT uPosition,UINT uFlags); | user32.dll | Cette fonction permet de supprimer un item de menu.
DeleteMetaFile | BOOL DeleteMetaFile(HMETAFILE hmf); | gdi32.dll | Cette fonction permet de supprimer un fichier de méta de format Windows ou le Handle d'un fichier méta de format Windows.
DeleteObject | BOOL DeleteObject(HGDIOBJ hObject); | gdi32.dll | Cette fonction permet de supprimer un crayon, une brosse, une police de caractères, un Bitmap ou une palette logique et d'ainsi libérer les ressources systèmes associé avec cette objet.
DeletePrinter | BOOL DeletePrinter(HANDLE hPrinter); | winspool.drv | Cette fonction permet de supprimer un objet d'imprimante.
DeregisterEventSource | BOOL DeregisterEventSource( HANDLE hEventLog ); | advapi32.dll | Cette fonction permet de fermer un identificateur Handle d'enregistrement d'événement retourner par une fonction «RegisterEventSource».
DescribePixelFormat | int DescribePixelFormat(HDC hdc, int iPixelFormat, UINT nBytes, LPPIXELFORMATDESCRIPTOR ppfd); | gdi32.dll | Cette fonction permet de demander les informations à propos du format d'un pixel spécifié d'un périphérique spécifié.
DestroyAcceleratorTable | BOOL DestroyAcceleratorTable(HACCEL hAccel); | user32.dll | Cette fonction permet de détruire la table d'accélérateur.
DestroyCaret | BOOL DestroyCaret(VOID); | user32.dll | Cette fonction permet de supprimer le caret courant.
DestroyCursor | BOOL DestroyCursor(HCURSOR hCursor); | user32.dll | Cette fonction permet de supprimer une image de pointeur de souris
DestroyIcon | BOOL DestroyIcon(HICON hIcon); | user32.dll | Cette fonction permet de supprimer une icône
DestroyMenu | BOOL DestroyMenu(HMENU hMenu); | user32.dll | Cette fonction permet de supprimer un menu contextuel
DestroyPrivateObjectSecurity | BOOL DestroyPrivateObjectSecurity( PSECURITY_DESCRIPTOR * ObjectDescriptor ); | advapi32.dll | Cette fonction permet d'effacer un descripteur de sécurité d'un objet serveur protégé.
DestroyWindow | BOOL DestroyWindow(HWND hWnd); | user32.dll | Cette fonction permet de supprimer une fenêtre
DeviceCapabilitiesExA | DWORD DeviceCapabilitiesA(LPCSTR pDriverName, LPCSTR pDeviceName, LPCSTR pPort, WORD iIndex, LPSTR pOutput, const DEVMODEA* DevMode); | gdi32.dll | Cette fonction permet de demander la compatibilité pour le pilote d'imprimante spécifié.
DeviceCapabilitiesExW | DWORD DeviceCapabilitiesExW(LPCWSTR pDriverName, LPCWSTR pDeviceName, LPCWSTR pPort, WORD iIndex, LPWSTR pOutput, CONST DEVMODEW* DevMode); | gdi32.dll | Cette fonction permet de demander la compatibilité pour le pilote d'imprimante spécifié avec des paramètres de chaine de caractères étendues (Unicode).
DeviceIoControl | BOOL DeviceIoControl( HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped ); | kernel32.dll | Cette fonction permet d'envoyer des codes de contrôles directement à un pilote de périphérique.
DialogBoxIndirectParamA | int DialogBoxIndirectParamA(HINSTANCE hInstance, LPCDLGTEMPLATE hDialogTemplate, HWND hWndParent, DLGPROC lpDialogFunc, LPARAM dwInitParam); | user32.dll | Cette fonction permet de créer une boite de dialogue modèle à l'aide d'un gabarit en mémoire et des paramètres spécifiés.
DialogBoxIndirectParamW | int DialogBoxIndirectParamW(HINSTANCE hInstance, LPCDLGTEMPLATE hDialogTemplate, HWND hWndParent, DLGPROC lpDialogFunc, LPARAM dwInitParam); | user32.dll | Cette fonction permet de créer une boite de dialogue modèle à l'aide d'un gabarit en mémoire et des paramètres spécifiés avec des parmètres de chaine de caractères étendues (Unicode).
DialogBoxParamA | int DialogBoxParamA(HINSTANCE hInstance, LPCTSTR lpTemplateName, HWND hWndParent, DLGPROC lpDialogFunc, LPARAM dwInitParam); | user32.dll | Cette fonction permet de créer une boite de dialogue modèle et des paramètres spécifiés.
DialogBoxParamW | int DialogBoxParamW(HINSTANCE hInstance, LPCWSTR lpTemplateName, HWND hWndParent, DLGPROC lpDialogFunc, LPARAM dwInitParam); | user32.dll | Cette fonction permet de créer une boite de dialogue modèle et des paramètres spécifiés avec des paramètres de chaine de caractères étendues (Unicode).
DisableThreadLibraryCalls | BOOL DisableThreadLibraryCalls( HMODULE hLibModule ); | kernel32.dll | Cette fonction permet de désactiver les avertissements «DLL_THREAD_ATTACH» et «DLL_THREAD_DETACH» des bibliothèques de lien dynamique (DLL) spécifié.
DisconnectNamedPipe | BOOL DisconnectNamedPipe( HANDLE hNamedPipe ); | kernel32.dll | Cette fonction permet de déconnecter le serveur de fin de tuyau nommé pour un processus client.
DiskCopyRunDll | int DiskCopyRunDll(IN LPSTR Unuse1,IN LPSTR Unuse2,IN LPSTR pszDriveNumber,IN LPSTR Unuse3); | diskcopy.dll | Cette fonction permet de lancer une boite de dialogue offrant la possibilité d'effectuer une copie d'unité de disquette
DispatchMessageA | LONG DispatchMessageA(CONST MSG * lpmsg); | user32.dll | Cette fonction permet de redistribuer le messages de fenêtre.
DispatchMessageW | LRESULT DispatchMessageW(const MSG* msg); | user32.dll | Cette fonction permet de redistribuer le messages de fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
DlgDirListA | int DlgDirListA(HWND hDlg, LPTSTR lpPathSpec, int nIDListBox, int nIDStaticPath, UINT uFileType); | user32.dll | Cette fonction permet de remplir une boite de dialogue avec une liste de fichiers et de répertoires.
DlgDirListW | int DlgDirListW(HWND hDlg, LPWSTR lpPathSpec, int nIDListBox, int nIDStaticPath, UINT uFileType); | user32.dll | Cette fonction permet de remplir une boite de dialogue avec une liste de fichiers et de répertoires avec des paramètres de chaine de caractères étendues (Unicode).
DlgDirListComboBoxA | int DlgDirListComboBoxA(HWND hDlg, LPTSTR lpPathSpec, int nIDComboBox, int nIDStaticPath, UINT uFiletype); | user32.dll | Cette fonction permet de remplir une boite combinée (Combo Box) avec une liste de fichiers et de répertoires.
DlgDirListComboBoxW | int DlgDirListComboBoxW(HWND hDlg, LPWSTR lpPathSpec, int nIDComboBox, int nIDStaticPath, UINT uFiletype); | user32.dll | Cette fonction permet de remplir une boite combinée (Combo Box) avec une liste de fichiers et de répertoires avec des paramètres de chaine de caractères étendues (Unicode).
DlgDirSelectComboBoxExA | BOOL DlgDirSelectComboBoxExA(HWND hDlg, LPTSTR lpString, int nCount, int nIDComboBox); | user32.dll | Cette fonction permet de demander la sélection courante d'une boite combinée remplit par la fonction DlgDirListComboBox.
DlgDirSelectComboBoxExW | BOOL DlgDirSelectComboBoxExW(HWND hDlg, LPWSTR lpString, int nCount, int nIDComboBox); | user32.dll | Cette fonction permet de demander la sélection courante d'une boite combinée remplit par la fonction DlgDirListComboBox avec des paramètres de chaine de caractères étendues (Unicode).
DlgDirSelectExA | BOOL DlgDirSelectExA(HWND hDlg, LPTSTR lpString, int nCount, int nIDListBox); | user32.dll | Cette fonction permet de demander la sélection courante étendue d'une liste de boite.
DlgDirSelectExW | BOOL DlgDirSelectExW(HWND hDlg, LPWSTR lpString, int nCount, int nIDListBox); | user32.dll | Cette fonction permet de demander la sélection courante étendue d'une liste de boite avec des paramètres de chaine de caractères étendues (Unicode).
DllGetVersion | HRESULT CALLBACK DllGetVersion(DLLVERSIONINFO *pdvi); | shell32.dll | Cette fonction permet de demander les informations de la version de DLL
DosDateTimeToFileTime | BOOL DosDateTimeToFileTime( WORD wFatDate, WORD wFatTime, LPFILETIME lpFileTime ); | kernel32.dll | Cette fonction permet de convertir la valeur d'une date et d'une heure du DOS en un temps fichier de 64 bits.
DPtoLP | BOOL DPtoLP(HDC hdc, LPPOINT lpPoints, int nCount); | gdi32.dll | Cette fonction permet d'effectuer la conversion des coordonnées d'un périphérique en coordonnées logique.
DragAcceptFiles | VOID DragAcceptFiles(HWND hWnd,BOOL fAccept); | shell32.dll | Cette fonction permet de fixer l'état du drapeau indiquant si la fenêtre accepte les fichiers glissées
DragDetect | BOOL DragDetect(HWND hwnd, POINT pt); | user32.dll | Cette fonction permet de déterminer quand une fonction de glisser-déposer (drag and drop) est mis en place.
DragObject | DWORD DragObject(HWND hWndScope, HWND hWndObj, UINT wObjType, DWORD hOfStruct, HCURSOR hDragCursor); | user32.dll | Cette fonction permet d'intéger une composante serveur de glisser-déposer (drag and drop).
DragFinish | VOID DragFinish(HDROP hDrop); | shell32.dll | Cette fonction permet de restituer la mémoire employé pour le transfert des noms de fichiers vers une fenêtre d'application
DragQueryFile | UINT DragQueryFile(HDROP hDrop,UINT iFile,LPTSTR lpszFile,UINT cch); | shell32.dll | Cette fonction permet de demander les noms de fichiers lors d'un transfert des noms de fichiers vers une fenêtre d'application
DrawAnimatedRects | BOOL DrawAnimatedRects(HWND hwnd, int idAni, CONST RECT * lprcFrom, CONST RECT * lprcTo); | user32.dll | Cette fonction permet de dessiner un cadre de rectangle et d'animer l'icône ouvert.
DrawCaption | void DrawCaption(HDC hdc, LPRECT lprc, HFONT hfont, HICON hicon, LPSTR lpszText, WORD wFlags); | user32.dll | Cette fonction permet de dessiner une fenêtre de légende.
DrawEdge | BOOL DrawEdge(HDC hdc, LPRECT qrc, UINT edge, UINT grfFlags); | user32.dll | Cette fonction permet de dessiner une ou plusieurs bordures d'un rectangle.
DrawEscape | int DrawEscape(HDC hdc, int nEscape, int cbInput, LPCSTR lpszInData); | gdi32.dll | Cette fonction permet d'accéder à la compatibilité de dessinage de l'affichage d'un vidéo n'étant pas directement disponible par le GDI (Graphics Device Interface).
DrawFocusRect | BOOL DrawFocusRect(HDC hDC, CONST RECT * lprc); | user32.dll | Cette fonction permet de dessiner un rectangle avec un style indiquant qu'il est focusé.
DrawFrameControl | BOOL DrawFrameControl(HDC hdc, LPRECT lprc, UINT uType, UINT uState); | user32.dll | Cette fonction permet de dessiner un cadre de contrôle pour le type et le style spécifié.
DrawIcon | BOOL DrawIcon(HDC hDC,int X,int Y,HICON hIcon); | user32.dll | Cette fonction permet d'afficher une icône dans la fenêtre d'application du contexte de l'objet
DrawIconEx | BOOL DrawIconEx(HDC hdc, int xLeft, int yTop, HICON hIcon, int cxWidth, int cyWidth, UINT istepIfAniCur, HBRUSH hbrFlickerFreeDraw, UINT diFlags); | user32.dll | Cette fonction permet d'afficher un icône ou un curseur dans une région client d'une fenêtre d'un périphérique de contexte donnée, d'effectuer des opérations de traitement de trame spécifié et d'étirement ou de compression de l'icône ou le curseur spécifié.
DrawMenuBar | BOOL DrawMenuBar(HWND hWnd); | user32.dll | Cette fonction permet de réafficher la barre de menu de la fenêtre d'application.
DrawStateA | BOOL DrawStateA(HDC hdc, HBRUSH hbr, DRAWSTATEPROC lpOutputFunc, LPARAM lData, WPARAM wData, int x, int y, int cx, int cy, UINT fuFlags); | user32.dll | Cette fonction permet d'afficher une image et d'appliquer un effet visuel indiquant son état, laquelle peut être désactiver ou un état par défaut.
DrawStateW | BOOL DrawStateW(HDC hdc, HBRUSH hbr, DRAWSTATEPROC lpOutputFunc, LPARAM lData, WPARAM wData, int x, int y, int cx, int cy, UINT fuFlags); | user32.dll | Cette fonction permet d'afficher une image et d'appliquer un effet visuel indiquant son état, laquelle peut être désactiver ou un état par défaut avec des paramètres de chaine de caractères étendues (Unicode).
DrawTextA | int DrawTextA(HDC hDC, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat); | user32.dll | Cette fonction permet de dessiner le texte formatté dans le rectangle spécifié.
DrawTextW | int DrawTextW(HDC hDC, LPCWSTR lpString, int nCount, LPRECT lpRect, UINT uFormat); | user32.dll | Cette fonction permet de dessiner le texte formatté dans le rectangle spécifié avec des paramètres de chaine de caractères étendues (Unicode).
DrawTextExA | int DrawTextExA(HDC hdc, LPTSTR lpchText, int cchText, LPRECT lprc, UINT dwDTFormat, LPDRAWTEXTPARAMS lpDTParams); | user32.dll | Cette fonction permet de dessiner le texte formatté dans le rectangle spécifié avec des paramètres supplémentaires.
DrawTextExW | int DrawTextExW(HDC hdc, LPWSTR lpchText, int cchText, LPRECT lprc, UINT dwDTFormat, LPDRAWTEXTPARAMS lpDTParams); | user32.dll | Cette fonction permet de dessiner le texte formatté dans le rectangle spécifié avec des paramètres supplémentaires et des chaines de caractères étendues (Unicode).
DuplicateHandle | BOOL DuplicateHandle( HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle, LPHANDLE lpTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions ); | kernel32.dll | Cette fonction permet de dupliquer un objet d'identificateur Handle existant.
DuplicateIcon | HICON DuplicateIcon(HINSTANCE hInst,HICON hIcon); | shell32.dll | Cette fonction permet d'effectuer la création d'un nouvel icône identique à une autre icône spécifié.
DuplicateToken | BOOL DuplicateToken( HANDLE ExistingTokenHandle, SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, PHANDLE DuplicateTokenHandle ); | advapi32.dll | Cette fonction permet de créer un nouvel accès de jeton dupliqué sur un autre jeton déjà existant.
DuplicateTokenEx | BOOL DuplicateTokenEx( HANDLE hExistingToken, DWORD dwDesiredAccess, LPSECURITY_ATTRIBUTES lpTokenAttributes, SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, TOKEN_TYPE TokenType, PHANDLE phNewToken ); | advapi32.dll | Cette fonction permet d'effectuer la conversion d'un jeton spécifié par un jeton principal.
DvdLauncher | BOOL DvdLauncher(HWND HWnd,CHAR DriveLetter); | storprop.dll | Cette fonction permet de vérifier si la région que contient le DVD correspond avec celle du système d'exploitation.
Ellipse | BOOL Ellipse(HDC hdc,int nLeftRect,int nTopRect,int nRightRect,int nBottomRect); | gdi32.dll | Cette fonction permet d'effectuer l'afficher d'une ellipse (forme d'ovale).
EmptyClipboard | BOOL EmptyClipboard(VOID); | user32.dll | Cette fonction permet de vider complètement le tampon du presse-papier.
EnableMenuItem | BOOL EnableMenuItem(HMENU hMenu,UINT uIDEnableItem,UINT uEnable); | user32.dll | Cette fonction permet d'activer ou de désactiver un item de menu déroulant.
EnableScrollBar | BOOL EnableScrollBar(HWND hWnd,UINT wSBflags, UINT wArrows); | user32.dll | Cette fonction permet d'activer ou de désactiver les barres de défilement dans une fenêtre d'application.
EnableWindow | BOOL EnableWindow(HWND hWnd,BOOL bEnable); | user32.dll | Cette fonction permet d'activer ou de désactiver les événements clavier et souris et les contrôles dans une fenêtre d'application.
EndDeferWindowPos | BOOL EndDeferWindowPos(HDWP hWinPosInfo); | user32.dll | Cette fonction permet d'effectuer la simulation de mise-à-jour de position et de taille d'une ou plusieurs fenêtres dans un simple cycle de rafraichissement d'écran.
EndDialog | BOOL EndDialog(HWND hDlg, int nResult); | user32.dll | Cette fonction permet de cacher un modèle de boite de dialogue.
EndDoc | int EndDoc(HDC hdc); | gdi32.dll | Cette fonction permet d'indiquer que l'application a terminé d'envoyer des données au tampon d'impression de l'imprimante.
EndPage | int EndPage(HDC hdc); | gdi32.dll | Cette fonction permet d'indiquer que l'application a terminé d'envoyer une page de données au tampon d'impression de l'imprimante.
EndPaint | BOOL EndPaint(HWND hWnd, CONST PAINTSTRUCT *lpPaint); | user32.dll | Cette fonction permet de marquer la fin du traitement de peinture d'une fenêtre spécifié.
EndPath | BOOL EndPath(HDC hdc); | gdi32.dll | Cette fonction permet de fermer une braquette de chemin et sélectionne le chemin définit par la braquette dans le périphérique de contexte (DC) spécifié.
EndUpdateResourceA | BOOL EndUpdateResourceA( HANDLE hUpdate, BOOL fDiscard ); | kernel32.dll | Cette fonction permet de terminer la mise-à-jour d'une ressource d'un fichier exécutable.
EndUpdateResourceW | BOOL EndUpdateResourceW(HANDLE hUpdate,BOOL fDiscard); | kernel32.dll | Cette fonction permet de terminer la mise-à-jour d'une ressource d'un fichier exécutable avec des paramètres de chaine de caractères étendue (Unicode).
EnterCriticalSection | VOID EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection ); | kernel32.dll | Cette fonction permet d'attendre après un propriétaire d'objet de section critique spécifié.
EnumCalendarInfoA | BOOL EnumCalendarInfoA( CALINFO_ENUMPROC lpCalInfoEnumProc, LCID Locale, CALID Calendar, CALTYPE CalType ); | kernel32.dll | Cette fonction permet d'énumérer des informations de calendrier d'une région local spécifié
EnumCalendarInfoW | BOOL EnumCalendarInfoW( CALINFO_ENUMPROCW calinfoproc, LCID locale, CALID calendar, CALTYPE caltype ); | kernel32.dll | Cette fonction permet d'énumérer des informations de calendrier d'une région local spécifié avec des paramètres de chaine de caractères étendue (Unicode).
EnumChildWindows | BOOL EnumChildWindows(HWND hWndParent, WNDENUMPROC lpEnumFunc, LPARAM lParam); | user32.dll | Cette fonction permet d'énumérer les fenêtres enfants de la fenêtre parent en passant le Handle de chaque fenêtre enfant avec une fonction d'application utilisateur.
EnumClipboardFormats | UINT EnumClipboardFormats(UINT format); | user32.dll | Cette fonction permet de retourner les formats de presse-papier disponibles.
EnumDateFormatsA | BOOL EnumDateFormatsA( DATEFMT_ENUMPROC lpDateFmtEnumProc, LCID Locale, DWORD dwFlags ); | kernel32.dll | Cette fonction permet d'énumérer le format de la date courte ou longue disponible pour la région locale.
EnumDateFormatsW | BOOL EnumDateFormatsW( DATEFMT_ENUMPROCW lpDateFmtEnumProc, LCID Locale, DWORD dwFlags); | kernel32.dll | Cette fonction permet d'énumérer le format de la date courte ou longue disponible pour la région locale avec des paramètres de chaine de caractères étendue (Unicode).
EnumDesktopsA | BOOL EnumDesktopsA(HWINSTA hwinsta, DESKTOPENUMPROC lpEnumFunc, LPARAM lParam); | user32.dll | Cette fonction permet de demander l'énumération de tous les bureaux d'une fenêtre de travail associé avec un processus appelant.
EnumDesktopsW | BOOL EnumDesktopsW(HWINSTA hwinsta, DESKTOPENUMPROCW lpEnumFunc, LPARAM lParam); | user32.dll | Cette fonction permet de demander l'énumération de tous les bureaux d'une fenêtre de travail associé avec un processus appelant avec des paramètres de chaine de caractères étendues (Unicode).
EnumDesktopWindows | BOOL EnumDesktopWindows(HDESK hDesktop, WNDENUMPROC lpfn, LPARAM lParam); | user32.dll | Cette fonction permet de demander l'énumération de tous les bureaux passé par le Handle de chaque fenêtre, par l'entremise d'une fonction utilisateur.
EnumDisplaySettingsA | BOOL EnumDisplaySettingsA(LPCTSTR lpszDeviceName,DWORD iModeNum,LPDEVMODE lpDevMode); | user32.dll | Cette fonction permet de demander des informations sur le système d'affichage.
EnumDisplaySettingsW | BOOL EnumDisplaySettingsW(LPCWSTR lpszDeviceName, DWORD iModeNum, PDEVMODEW lpDevMode); | user32.dll | Cette fonction permet de demander des informations sur le système d'affichage avec des paramètres de chaine de caractères étendues (Unicode).
EnumEnhMetaFile | BOOL EnumEnhMetaFile(HDC hdc, HENHMETAFILE hemf, ENHMFENUMPROC lpEnhMetaFunc, LPVOID lpData, CONST RECT * lpRect); | gdi32.dll | Cette fonction permet d'énumérer les enregistrements d'un format étendue d'un méta fichier afin de demander chaques enregistrements et de les passés par une fonction utilisateur.
EnumFontFamiliesA | int EnumFontFamiliesA(HDC hdc, LPCTSTR lpszFamily, FONTENUMPROC lpEnumFontFamProc, LPARAM lParam); | gdi32.dll | Cette fonction permet d'énumérer les polices de caractères d'une famille de police de caractères spécifié étant disponible pour un périphérique spécifié.
EnumFontFamiliesW | int EnumFontFamiliesW(HDC hdc, PLOGFONTW lpszFamily, FONTENUMPROCW lpEnumFontFamProc, LPARAM lParam); | gdi32.dll | Cette fonction permet d'énumérer les polices de caractères d'une famille de police de caractères spécifié étant disponible pour un périphérique spécifié avec des paramètres de chaine de caractères étendues (Unicode).
EnumFontFamiliesExA | int EnumFontFamiliesExA(HDC hdc, LPLOGFONT lpLogfont, FONTENUMPROC lpEnumFontFamExProc, LPARAM lParam, DWORD dwFlags); | gdi32.dll | Cette fonction permet d'énumérer toutes les polices de caractères système correspondant aux caractéristiques de polices de caractères spécifié par une structure.
EnumFontFamiliesExW | int EnumFontFamiliesExW(HDC hdc, LPLOGFONTW lpLogfont, FONTENUMPROCW lpEnumFontFamExProc, LPARAM lParam, DWORD dwFlags); | gdi32.dll | Cette fonction permet d'énumérer toutes les polices de caractères système correspondant aux caractéristiques de polices de caractères spécifié par une structure avec des paramètres de chaine de caractères étendues (Unicode).
EnumFontsA | int EnumFontsA(HDC hdc, LPCTSTR lpFaceName, FONTENUMPROC lpFontFunc, LPARAM lParam); | gdi32.dll | Cette fonction permet d'énumérer les polices de caractères disponibles pour le périphérique spécifié.
EnumFontsW | int EnumFontsW(HDC hDC, LPCWSTR lpFaceName, FONTENUMPROCW FontFunc, LPARAM lParam); | gdi32.dll | Cette fonction permet d'énumérer les polices de caractères disponibles pour le périphérique spécifié avec des paramètres de chaine de caractères étendues (Unicode).
EnumICMProfilesA | int EnumICMProfilesA(HDC hdc, ICMENUMPROC lpICMEnumFunc, LPARAM lParam); | gdi32.dll | Cette fonction permet d'énumérer les différents profiles de couleurs étant supporté par le système pour le périphérique de contexte spécifié.
EnumICMProfilesW | int EnumICMProfilesW(HDC hDC, ICMENUMPROCW lpEnumICMProfilesFunc, LPARAM lParam); | gdi32.dll | Cette fonction permet d'énumérer les différents profiles de couleurs étant supporté par le système pour le périphérique de contexte spécifié avec des paramètres de chaine de caractères étendues (Unicode).
EnumJobsA | BOOL EnumJobsA( HANDLE hPrinter, DWORD FirstJob, DWORD NoJobs, DWORD Level, LPBYTE pJob, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned ); | winspool.drv | Cette fonction permet de définir les travaux d'impressions d'une imprimante.
EnumMetaFile | BOOL EnumMetaFile(HDC hdc, HMETAFILE hmf, MFENUMPROC lpMetaFunc, LPARAM lParam); | gdi32.dll | Cette fonction permet de demander d'énumérer les enregistrements de fichier méta de format Windows en utilisant une fonction d'application utilisateur.
EnumObjects | int EnumObjects(HDC hdc, int nObjectType, GOBJENUMPROC lpObjectFunc, LPARAM lParam); | gdi32.dll | Cette fonction permet d'énumérer les crayons et les brosses disponibles pour le périphérique de contexte spécifié.
EnumPortsA | BOOL EnumPortsA( LPTSTR pName, DWORD Level, LPBYTE pPorts, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned ); | winspool.drv | Cette fonction permet de demander les ports d'impressions d'imprimante disponible.
EnumPrintersA | BOOL EnumPrintersA( DWORD Flags, LPTSTR Name, DWORD Level, LPBYTE pPrinterEnum, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned ); | winspool.drv | Cette fonction permet de demander des informations sur une imprimante.
EnumPropsA | int EnumPropsA(HWND hWnd, PROPENUMPROC lpEnumFunc); | user32.dll | Cette fonction permet d'effectuer le passage de la liste des entrées de propriétés par l'intermédiaire d'une fonction utilisateur.
EnumPropsExA | int EnumPropsExA(HWND hWnd, PROPENUMPROCEX lpEnumFunc, LPARAM lParam); | user32.dll | Cette fonction permet d'effectuer le passage de la liste des entrées de propriétés par l'intermédiaire d'une fonction utilisateur avec des paramètres supplémentaires.
EnumPropsExW | int EnumPropsExW(HWND hWnd, PROPENUMPROCEXW lpEnumFunc, LPARAM lParam); | user32.dll | Cette fonction permet d'effectuer le passage de la liste des entrées de propriétés par l'intermédiaire d'une fonction utilisateur avec des paramètres supplémentaires et des chaines de caractères étendues (Unicode).
EnumPropsW | int EnumPropsW(HWND hWnd, PROPENUMPROCW lpEnumFunc); | user32.dll | Cette fonction permet d'effectuer le passage de la liste des entrées de propriétés par l'intermédiaire d'une fonction utilisateur avec des paramètres de chaine de caractères étendues (Unicode).
EnumResourceLanguagesA | BOOL EnumResourceLanguagesA( HMODULE hModule, LPCTSTR lpType, LPCTSTR lpName, ENUMRESLANGPROC lpEnumFunc, LONG lParam ); | kernel32.dll | Cette fonction permet de rechercher un module pour chaque ressource d'un type spécifié et de passé le langage pour chaque ressource trouvé dans une fonction définie en arrière plan.
EnumResourceLanguagesW | BOOL EnumResourceLanguagesW( HMODULE hModule, LPCWSTR lpType, LPCWSTR lpName, ENUMRESLANGPROC lpEnumFunc, LONG_PTR lParam ); | kernel32.dll | Cette fonction permet de rechercher un module pour chaque ressource d'un type spécifié et de passé le langage pour chaque ressource trouvé dans une fonction définie en arrière plan avec des paramètres de chaine de caractères étendue (Unicode).
EnumResourceNamesA | BOOL EnumResourceNamesA( HINSTANCE hModule, LPCTSTR lpszType, ENUMRESNAMEPROC lpEnumFunc, LONG lParam ); | kernel32.dll | Cette fonction permet de rechercher un module pour chaque ressource d'un type spécifié et de passé le langage pour chaque ressource trouvé dans une fonction d'application définie en arrière plan.
EnumResourceNamesW | BOOL EnumResourceNamesW( HMODULE hModule, LPCWSTR lpszType, ENUMRESNAMEPROC lpEnumFunc, LONG_PTR lParam ); | kernel32.dll | Cette fonction permet de rechercher un module pour chaque ressource d'un type spécifié et de passé le langage pour chaque ressource trouvé dans une fonction d'application définie en arrière plan avec des paramètres de chaine de caractères étendue (Unicode).
EnumResourceTypesA | BOOL EnumResourceTypesA( HMODULE hModule, ENUMRESTYPEPROC lpEnumFunc, LONG lParam ); | kernel32.dll | Cette fonction permet de rechercher un module pour une ressource et de passé chaque type de ressource dans une fonction d'application définie en arrière plan.
EnumResourceTypesW | BOOL EnumResourceTypesW( HMODULE hModule, ENUMRESTYPEPROC lpEnumFunc, , LONG_PTR lParam ); | kernel32.dll | Cette fonction permet de rechercher un module pour une ressource et de passé chaque type de ressource dans une fonction d'application définie en arrière plan avec des paramètres de chaine de caractères étendue (Unicode).
EnumSystemCodePagesA | BOOL EnumSystemCodePagesA( CODEPAGE_ENUMPROC lpCodePageEnumProc, DWORD dwFlags ); | kernel32.dll | Cette fonction permet d'énumérer un code de page installé ou supporté par le système d'exploitation.
EnumSystemCodePagesW | BOOL EnumSystemCodePagesW( CODEPAGE_ENUMPROCW lpfnCodePageEnum, DWORD flags ); | kernel32.dll | Cette fonction permet d'énumérer un code de page installé ou supporté par le système d'exploitation avec des paramètres de chaine de caractères étendue (Unicode).
EnumSystemLocalesA | BOOL EnumSystemLocalesA( LOCALE_ENUMPROC lpLocaleEnumProc, DWORD dwFlags ); | kernel32.dll | Cette fonction permet d'énumérer la région locale installé ou supporté par le système d'exploitation.
EnumSystemLocalesW | BOOL EnumSystemLocalesW( LOCALE_ENUMPROCW lpfnLocaleEnum, DWORD dwFlags ); | kernel32.dll | Cette fonction permet d'énumérer la région locale installé ou supporté par le système d'exploitation avec des paramètres de chaine de caractères étendue (Unicode).
EnumTimeFormatsA | BOOL EnumTimeFormatsA( TIMEFMT_ENUMPROC lpTimeFmtEnumProc, LCID Locale, DWORD dwFlags ); | kernel32.dll | Cette fonction permet d'énumérer le format de temps disponible pour la région locale.
EnumTimeFormatsW | BOOL EnumTimeFormatsW( TIMEFMT_ENUMPROCW lpTimeFmtEnumProc, LCID Locale, DWORD dwFlags ); | kernel32.dll | Cette fonction permet d'énumérer le format de temps disponible pour la région locale avec des paramètres de chaine de caractères étendue (Unicode).
EnumThreadWindows | BOOL EnumThreadWindows(DWORD dwThreadId, WNDENUMPROC lpfn, LPARAM lParam); | user32.dll | Cette fonction permet d'effectuer l'énumération de toutes les fenêtres non-enfant associé avec un processus par passage d'un Handle pour chaque fenêtre avec une fonction d'application utilisateur.
EnumWindows | BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam); | user32.dll | Cette fonction permet d'effectuer le passage des fenêtres parent par l'intermédiaire d'une fonction utilisateur.
EnumWindowStationsA | BOOL EnumWindowStationsA(WINSTAENUMPROC lpEnumFunc, LPARAM lParam); | user32.dll | Cette fonction permet d'effectuer l'énumération de toutes les fenêtres de station dans le système par passage du nom pour chaque fenêtre de station avec une fonction d'application utilisateur.
EnumWindowStationsW | BOOL EnumWindowStationsW(WINSTAENUMPROCW EnumFunc, LPARAM Context); | user32.dll | Cette fonction permet d'effectuer l'énumération de toutes les fenêtres de station dans le système par passage du nom pour chaque fenêtre de station avec une fonction d'application utilisateur et des paramètres de chaine de caractères étendues (Unicode).
EqualPrefixSid | BOOL EqualPrefixSid( PSID pSid1, PSID pSid2 ); | advapi32.dll | Cette fonction permet de vérifie sur les valeurs de deux préfixes SID (identificateur de sécurité) sont égales à l'exception de la dernière valeur de sous-autorité.
EqualRect | BOOL EqualRect(CONST RECT *lprc1, CONST RECT *lprc2); | user32.dll | Cette fonction permet de demander lequel des deux rectangles spécifiés sont égales par comparaison des coordonnées de leur coin supérieur gauche et de leur coin inférieur droit.
EqualRgn | BOOL EqualRgn(HRGN hSrcRgn1, HRGN hSrcRgn2); | gdi32.dll | Cette fonction permet de demander lequel des deux régions spécifiés sont égales par comparaison des coordonnées de leur coin supérieur gauche et de leur coin inférieur droit.
EqualSid | BOOL EqualSid( PSID pSid1, PSID pSid2 ); | advapi32.dll | Cette fonction permet de vérifie sur les valeurs de deux préfixes SID (identificateur de sécurité) sont égales.
EraseTape | DWORD EraseTape( HANDLE hDevice, DWORD dwEraseType, BOOL bImmediate ); | kernel32.dll | Cette fonction permet de tout supprimer ou supprimer des parties de bande magnétique de sauvegarde.
Escape | int Escape(HDC hdc, int nEscape, int cbInput, LPCSTR lpvInData, LPVOID lpvOutData); | gdi32.dll | Cette fonction permet d'activer les applications de compatibilité d'accès d'un périphérique particulier non accessible directement par le GDI.
EscapeCommFunction | BOOL EscapeCommFunction( HANDLE hFile, DWORD dwFunc ); | kernel32.dll | Cette fonction permet d'effectuer directement une communication d'un périphérique spécifié à une fonction étendue.
ExcludeClipRect | int ExcludeClipRect(HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect); | gdi32.dll | Cette fonction permet de créer une nouvelle région de Clip consistant en une minuscule région existante du rectangle spécifié.
ExcludeUpdateRgn | int ExcludeUpdateRgn(HDC hDC, HWND hWnd); | user32.dll | Cette fonction permet d'exclure la région mise-à-jour d'une région de capture.
ExitProcess | VOID ExitProcess( UINT uExitCode ); | kernel32.dll | Cette fonction permet de terminer un processus et tous ses processus légers.
ExitThread | VOID ExitThread( DWORD dwExitCode ); | kernel32.dll | Cette fonction permet de terminer un processus léger.
ExitWindowsEx | BOOL ExitWindowsEx(UINT uFlags,DWORD dwReserved); | user32.dll | Cette fonction permet d'effectuer l'arrêt ou le redémarrage de Windows et/ou de l'ordinateur.
ExpandEnvironmentStringsA | DWORD ExpandEnvironmentStringsA( LPCTSTR lpSrc, LPTSTR lpDst, DWORD nSize ); | kernel32.dll | Cette fonction permet d'étendre une chaine de caractères de variable d'environnement et de remplacer leurs valeurs par celle correspondante.
ExpandEnvironmentStringsW | DWORD ExpandEnvironmentStringsW( LPCWSTR src, LPWSTR dst, DWORD len ); | kernel32.dll | Cette fonction permet d'étendre une chaine de caractères de variable d'environnement et de remplacer leurs valeurs par celle correspondante avec des paramètres de chaine de caractères étendue (Unicode).
ExtCreatePen | HPEN ExtCreatePen(DWORD dwPenStyle, DWORD dwWidth, CONST LOGBRUSH * lplb, DWORD dwStyleCount, CONST DWORD * lpStyle); | gdi32.dll | Cette focntion permet de créer un crayon cosmétique ou géométrique logique ayant les attributs de style, de largeur et brosses spécifiés.
ExtCreateRegion | HRGN ExtCreateRegion(CONST XFORM * lpXform, DWORD nCount, CONST RGNDATA * lpRgnData); | gdi32.dll | Cette fonction permet de créer une région avec la région et la transformation de données spécifié.
ExtEscape | int ExtEscape(HDC hdc, int nEscape, int cbInput, LPCSTR lpszInData, int cbOutput, LPSTR lpszOutData); | gdi32.dll | Cette fonction permet d'allouer les applications vers une capacité d'accès d'un périphérique particulier n'étant pas disponible pour le GDI.
ExtFloodFill | BOOL ExtFloodFill(HDC hdc, int nXStart, int nYStart, COLORREF crColor, UINT fuFillType); | gdi32.dll | Cette fonction permet de remplir une région d'une surface d'affichage avec la brosse courante.
ExtractAssociatedIconA | HICON ExtractAssociatedIconA( HINSTANCE hInst, LPTSTR lpIconPath, LPWORD lpiIcon ); | shell32.dll | Cette fonction permet de demander le Handle dans icône indexé dans un fichier.
ExtractIconA | HICON ExtractIconA(HINSTANCE hInst,LPCTSTR lpszExeFileName,UINT nIconIndex); | shell32.dll | Cette fonction permet de demander une unique icône dans un fichier.
ExtractIconExA | UINT ExtractIconExA(LPCTSTR lpszFile,int nIconIndex,HICON *phiconLarge,HICON *phiconSmall,UINT nIcons); | shell32.dll | Cette fonction permet de demander plusieurs icônes dans un fichier.
ExtSelectClipRgn | int ExtSelectClipRgn(HDC hdc, HRGN hrgn, int fnMode); | gdi32.dll | Cette fonction permet de combiner la région spécifié avec la région de capture courante en utilisant le mode spécifié.
ExtTextOutA | BOOL ExtTextOutA(HDC hdc, int X, int Y, UINT fuOptions, CONST RECT *lprc, LPCTSTR lpString, UINT cbCount, CONST INT * lpDx ); | gdi32.dll | Cette fonction permet de dessiner une chaine de caractères en utilisant la police de caractères courante.
ExtTextOutW | BOOL ExtTextOutW(HDC hdc, INT x, INT y, UINT flags, const RECT* lprect, LPCWSTR str, UINT count, const INT* lpDx); | gdi32.dll | Cette fonction permet de dessiner une chaine de caractères en utilisant la police de caractères courante avec des paramètres de chaines de caractères étendues (Unicode).
FatalAppExitA | VOID FatalAppExitA(UINT uAction,LPCTSTR lpMessageText); | kernel32.dll | Cette fonction permet d'afficher la boite de dialogue d'erreur fatal amenant la terminaison anormale du programme.
FatalAppExitW | void FatalAppExitW( UINT uAction, LPCWSTR lpMessageText ); | kernel32.dll | Cette fonction permet d'afficher la boite de dialogue d'erreur fatal amenant la terminaison anormale du programme avec des paramètres de chaine de caractères étendue (Unicode).
FatalExit | VOID FatalExit( int ExitCode ); | kernel32.dll | Cette fonction permet de transférer le contrôle d'exécution au débogueur.
FileTimeToDosDateTime | BOOL FileTimeToDosDateTime( CONST FILETIME * lpFileTime, LPWORD lpFatDate, LPWORD lpFatTime ); | kernel32.dll | Cette fonction permet de convertir un temps de fichier 64 bits en des valeurs de date et temps.
FileTimeToLocalFileTime | BOOL FileTimeToLocalFileTime( CONST FILETIME * lpFileTime, LPFILETIME lpLocalFileTime ); | kernel32.dll | Cette fonction permet d'effectuer la conversion du temps d'un fichier de format UTC (Coordonnée de Temps Universelle) en format de temps local.
FileTimeToSystemTime | BOOL FileTimeToSystemTime( CONST FILETIME * lpFileTime, LPSYSTEMTIME lpSystemTime ); | kernel32.dll | Cette fonction permet de convertir un temps fichier de 64 bits à un temps de format système.
FillConsoleOutputAttribute | BOOL FillConsoleOutputAttribute( HANDLE hConsoleOutput, WORD wAttribute, DWORD nLength, COORD dwWriteCoord, LPDWORD lpNumberOfAttrsWritten ); | kernel32.dll | Cette fonction permet de fixer la couleur de texte et d'arrière plan pour un nombre spécifié de caractères en commençant au coordonnées du tampon d'écran spécifié.
FillConsoleOutputCharacterA | BOOL FillConsoleOutputCharacterA( HANDLE hConsoleOutput, TCHAR cCharacter, DWORD nLength, COORD dwWriteCoord, LPDWORD lpNumberOfCharsWritten ); | kernel32.dll | Cette fonction permet d'écrire un caractère dans le tampon d'écran un certain nombre de fois en commençant au coordonnées du tampon d'écran spécifié.
FillConsoleOutputCharacterW | BOOL FillConsoleOutputCharacterW( HANDLE hConsoleOutput, WCHAR ch, DWORD length, COORD coord, LPDWORD lpNumCharsWritten ); | kernel32.dll | Cette fonction permet d'écrire un caractère dans le tampon d'écran un certain nombre de fois en commençant au coordonnées du tampon d'écran spécifié avec des paramètres de chaine de caractères étendue (Unicode).
FillPath | BOOL FillPath(HDC hdc); | gdi32.dll | Cette fonction permet de fermer n'importe quel figures ouvertes dans le chemin courant et remplis l'intérieur du chemin en utilisant la brosses courante et le mode de remplissage de polygone.
FillRect | int FillRect(HDC hDC, CONST RECT *lprc, HBRUSH hbr); | user32.dll | Cette fonction permet de remplir un rectangle avec la brosse spécifié.
FillRgn | BOOL FillRgn(HDC hdc, HRGN hrgn, HBRUSH hbr); | gdi32.dll | Cette fonction permet de remplir la région avec le brosse spécifié.
FindAtomA | ATOM FindAtomA( LPCTSTR lpString ); | kernel32.dll | Cette fonction permet de rechercher dans un table d'Atom local la chaine de caractères spécifié et demande l'Atom associé avec cette chaine de caractères.
FindAtomW | ATOM FindAtomW( LPCWSTR str ); | kernel32.dll | Cette fonction permet de rechercher dans un table d'Atom local la chaine de caractères spécifié et demande l'Atom associé avec cette chaine de caractères avec des paramètres de chaine de caractères étendue (Unicode).
FindClose | BOOL FindClose(HANDLE hFindFile); | kernel32.dll | Cette fonction permet d'indiquer que la recherche des fichiers et répertoires que contient un répertoire est terminé.
FindCloseChangeNotification | BOOL FindCloseChangeNotification(HANDLE hChangeHandle); | kernel32.dll | Cette fonction permet d'indiquer que la recherche et modifications des fichiers et répertoires que contient un répertoire est terminé.
FindExecutableA | HINSTANCE FindExecutableA(LPCTSTR lpFile,LPCTSTR lpDirectory,LPTSTR lpResult); | shell32.dll | Cette fonction permet de demander le fichier devant être exécuté lorsque la sorte d'extension de fichier est rencontré.
FindFirstChangeNotificationA | HANDLE FindFirstChangeNotificationA( LPCTSTR lpPathName, BOOL bWatchSubtree, DWORD dwNotifyFilter ); | kernel32.dll | Cette fonction permet de créer un identificateur Handle d'avertissement de changement et fixe la condition d'avertissement de changement initial.
FindFirstChangeNotificationW | HANDLE FindFirstChangeNotificationW(LPCWSTR lpPathName, BOOL bWatchSubtree, DWORD dwNotifyFilter); | kernel32.dll | Cette fonction permet de créer un identificateur Handle d'avertissement de changement et fixe la condition d'avertissement de changement initial avec des paramètres de chaine de caractères étendue (Unicode).
FindFirstChangeNotification | HANDLE FindFirstChangeNotification( LPCTSTR lpPathName, BOOL bWatchSubtree, DWORD dwNotifyFilter ); | kernel32.dll | Cette fonction permet de spécifier une recherche et modifications des fichiers et répertoires que contient un répertoire.
FindFirstFileA | HANDLE FindFirstFileA(LPCTSTR lpFileName,LPWIN32_FIND_DATA lpFindFileData); | kernel32.dll | Cette fonction permet de spécifier une recherche des fichiers et répertoires que contient un répertoire.
FindFirstFileExA | HANDLE FindFirstFileExA( LPCSTR lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, PVOID lpFindFileData, FINDEX_SEARCH_OPS fSearchOp, PVOID lpSearchFilter, DWORD dwAdditionalFlags ); | kernel32.dll | Cette fonction permet de rechercher dans un répertoire un fichier ou un sous-répertoire avec une correspondance de nom et d'attribut spécifié.
FindFirstFileExW | HANDLE FindFirstFileExW( LPCWSTR lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, PVOID lpFindFileData, FINDEX_SEARCH_OPS fSearchOp, PVOID lpSearchFilter, DWORD dwAdditionalFlags ); | kernel32.dll | Cette fonction permet de rechercher dans un répertoire un fichier ou un sous-répertoire avec une correspondance de nom et d'attribut spécifié avec des paramètres de chaine de caractères étendue (Unicode).
FindFirstFileW | HANDLE FindFirstFileW( LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData ); | kernel32.dll | Cette fonction permet de spécifier une recherche des fichiers et répertoires que contient un répertoire avec des paramètres de chaine de caractères étendue (Unicode).
FindFirstFreeAce | BOOL FindFirstFreeAce( PACL pAcl, LPVOID *pAce ); | advapi32.dll | Cette fonction permet de retourner un pointeur sur le premier octet de libre sur une liste de contrôle d'accès (ACL).
FindNextChangeNotification | BOOL FindNextChangeNotification(HANDLE hChangeHandle); | kernel32.dll | Cette fonction permet demander l'entrée suivant lors de la recherche et modifications des fichiers et répertoire que contient un répertoire.
FindNextFileA | BOOL FindNextFileA(HANDLE hFindFile,LPWIN32_FIND_DATA lpFindFileData); | kernel32.dll | Cette fonction permet demander l'entrée suivante lors de la recherche des fichiers et répertoire que contient un répertoire.
FindNextFileW | BOOL FindNextFileW( HANDLE hFindFile, LPWIN32_FIND_DATAW lpFindFileData ); | kernel32.dll | Cette fonction permet demander l'entrée suivante lors de la recherche des fichiers et répertoire que contient un répertoire avec des paramètres de chaine de caractères étendue (Unicode).
FindResourceA | HRSRC FindResourceA( HMODULE hModule, LPCTSTR lpName, LPCTSTR lpType ); | kernel32.dll | Cette fonction permet de demander l'emplacement d'une ressource avec un nom et un type spécifié dans un module spécifié.
FindResourceW | HRSRC FindResourceW( HINSTANCE hModule, LPCWSTR lpName, LPCWSTR lpType ); | kernel32.dll | Cette fonction permet de demander l'emplacement d'une ressource avec un nom et un type spécifié dans un module spécifié avec des paramètres de chaine de caractères étendue (Unicode).
FindResourceExA | HRSRC FindResourceExA( HMODULE hModule, LPCTSTR lpType, LPCTSTR lpName, WORD wLanguage ); | kernel32.dll | Cette fonction permet de demander l'emplacement d'une ressource avec un type, un nom et une langue spécifié dans un module spécifié.
FindResourceExW | HRSRC FindResourceExW( HINSTANCE hModule, LPCWSTR lpType, LPCWSTR lpName, WORD wLanguage ); | kernel32.dll | Cette fonction permet de demander l'emplacement d'une ressource avec un type, un nom et une langue spécifié dans un module spécifié avec des paramètres de chaine de caractères étendue (Unicode).
FindWindowA | HWND FindWindowA(LPCTSTR lpClassName, LPCTSTR lpWindowName); | user32.dll | Cette fonction permet de demander le Handle de la première fenêtre d'application correspondant au nom de la fenêtre.
FindWindowExA | HWND FindWindowExA(HWND hwndParent,HWND hwndChildAfter,LPCTSTR lpszClass,LPCTSTR lpszWindow); | user32.dll | Cette fonction permet de demander le Handle d'une fenêtre enfant d'une fenêtre d'application.
FindWindowExW | HWND FindWindowExW(HWND hwndParent,HWND hwndChildAfter, LPCWSTR lpszClass, LPCWSTR lpszWindow); | user32.dll | Cette fonction permet de demander le Handle d'une fenêtre enfant d'une fenêtre d'application avec des paramètres de chaine de caractères étendues (Unicode).
FindWindowW | HWND FindWindowW(LPCWSTR lpClassName, LPCWSTR lpWindowName); | user32.dll | Cette fonction permet de demander le Handle de la première fenêtre d'application correspondant au nom de la fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
FlashWindow | BOOL FlashWindow(HWND hWnd,BOOL bInvert); | user32.dll | Cette fonction permet de faire clignoter une fois une fenêtre d'application.
FlashWindowEx | BOOL FlashWindowEx(PFLASHWINFO pfwi); | user32.dll | Cette fonction permet de faire clignoter plusieurs fois une fenêtre d'application.
FlattenPath | BOOL FlattenPath(HDC hdc); | gdi32.dll | Cette fonction permet de transformer n'importe quel courbe d'un chemin étant sélectionné dans le périphérique de contexte courant de façon à tourner chaque courbe une séquences de lignes.
FloodFill | BOOL FloodFill(HDC hdc, int nXStart, int nYStart, COLORREF crFill); | gdi32.dll | Cette fonction permet d'effectuer le remplissage d'une région de la surface de l'écran en utilisant la brosse courante.
FlushConsoleInputBuffer | BOOL FlushConsoleInputBuffer( HANDLE hConsoleInput ); | kernel32.dll | Cette fonction permet de vider le tampon d'entrée de la console.
FlushFileBuffers | BOOL FlushFileBuffers( HANDLE hFile ); | kernel32.dll | Cette fonction permet d'effacer le tampon pour le fichier spécifié et provoquer l'écriture des données dans le fichier.
FlushInstructionCache | BOOL FlushInstructionCache( HANDLE hProcess, LPCVOID lpBaseAddress, DWORD dwSize ); | kernel32.dll | Cette fonction permet de vider les instructions en cache pour le processus spécifié.
FlushViewOfFile | BOOL FlushViewOfFile( LPCVOID lpBaseAddress, DWORD dwNumberOfBytesToFlush ); | kernel32.dll | Cette fonction permet d'écrire sur le disque un rang d'octets dans une vue d'un fichier.
FoldStringA | int FoldStringA( DWORD dwMapFlags, LPCTSTR lpSrcStr, int cchSrc, LPTSTR lpDestStr, int cchDest ); | kernel32.dll | Cette fonction permet de cartographier une chaine de caractères vers une autre en effectuant les options de transformations spécifié.
FoldStringW | INT FoldStringW( DWORD dwFlags, LPCWSTR src, INT srclen, LPWSTR dst, INT dstlen ); | kernel32.dll | Cette fonction permet de cartographier une chaine de caractères vers une autre en effectuant les options de transformations spécifié avec des paramètres de chaine de caractères étendue (Unicode).
FormatMessageA | DWORD FormatMessageA( DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPTSTR lpBuffer, DWORD nSize, va_list * Arguments ); | kernel32.dll | Cette fonction permet de demander un message affecté par un certain format.
FormatMessageW | DWORD FormatMessageW( DWORD dwFlags, PCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPWSTR lpBuffer, DWORD nSize, va_list * Arguments ); | kernel32.dll | Cette fonction permet de demander le dernier message d'erreur contenu dans le fichier d'erreur système.
FrameRect | int FrameRect(HDC hDC, CONST RECT * lprc, HBRUSH hbr); | user32.dll | Cette fonction permet de dessiner une bordure autour d'un rectangle, en utilisant la brosse spécifié.
FrameRgn | BOOL FrameRgn(HDC hdc, HRGN hrgn, HBRUSH hbr, int nWidth, int nHeight); | gdi32.dll | Cette fonction permet de dessiner une bordure entourant une région spécifié et en utilisant la brosse spécifié.
FreeConsole | BOOL FreeConsole(VOID); | kernel32.dll | Cette fonction permet de libérer une console existante.
FreeDDElParam | BOOL FreeDDElParam(UINT msg, LONG lParam); | user32.dll | Cette fonction permet de libérer la mémoire spécifié d'un message DDE posté.
FreeEnvironmentStringsA | BOOL FreeEnvironmentStringsA( LPTSTR lpszEnvironmentBlock ); | kernel32.dll | Cette fonction permet de libérer un bloc de chaine de caractères d'environnement.
FreeEnvironmentStringsW | BOOL FreeEnvironmentStringsW( LPWSTR lpszEnvironmentBlock ); | kernel32.dll | Cette fonction permet de libérer un bloc de chaine de caractères d'environnement avec des paramètres de chaine de caractères étendue (Unicode).
FreeLibrary | BOOL FreeLibrary(HMODULE hModule); | kernel32.dll | Cette fonction permet d'incrémenter un compteur de référence pour un chargement de module de lien dynamique (DLL).
FreeLibraryAndExitThread | VOID FreeLibraryAndExitThread( HMODULE hLibModule, DWORD dwExitCode ); | kernel32.dll | Cette fonction permet de décrémenté le compteur de référence d'une des bibliothèque de liens dynamique (DLL) et appel alors la fonction «ExitThread».
FreeResource | BOOL FreeResource(HGLOBAL hResource); | kernel32.dll | Cette fonction permet de libérer une ressource.
FreeSid | PVOID FreeSid( PSID pSid ); | advapi32.dll | Cette fonction permet de libérer un identificateur de sécurité (SID) précédemment alloué par la fonction «AllocateAndInitializeSid».
FtpCreateDirectoryA | BOOL FtpCreateDirectory( IN HINTERNET hFtpSession, IN LPCTSTR lpszDirectory ); | wininet.dll | Cette fonction permet d'effectuer la création d'un répertoire sur un protocole FTP.
FtpOpenFileA | HINTERNET FtpOpenFileA( HINTERNET hConnect, LPCTSTR lpszFileName, DWORD dwAccess, DWORD dwFlags, DWORD dwContext ); | wininet.dll | Cette fonction permet d'ouvrir un fichier avec une chaine de caractères ASCIZ sur un protocole FTP.
FtpOpenFileW | static void FtpOpenFileW(WORKREQUEST* workRequest); | wininet.dll | Cette fonction permet d'ouvrir un fichier avec une chaine de caractères étendue (Unicode) sur un protocole FTP.
GdiComment | BOOL GdiComment(HDC hdc, UINT cbSize, CONST BYTE * lpData); | gdi32.dll | Cette fonction permet de copier un commentaire d'un tampon vers un méta fichier spécifié de format étendue.
GdiFlush | BOOL GdiFlush(VOID); | gdi32.dll | Cette fonction permet de vider le traitement en lot courant du processus appelant.
GdiGetBatchLimit | DWORD GdiGetBatchLimit(VOID); | gdi32.dll | Cette fonction permet de retourner le nombre maximum d'appels de fonction étant accumulé dans le traitement par lot courant du processus léger courant.
GdiSetBatchLimit | DWORD GdiSetBatchLimit(DWORD dwLimit); | gdi32.dll | Cette fonction permet de fixer le nombre maximum d'appels de fonction étant accumulé dans le traitement par lot courant du processus léger courant.
GenerateConsoleCtrlEvent | BOOL GenerateConsoleCtrlEvent( DWORD dwCtrlEvent, DWORD dwProcessGroupId ); | kernel32.dll | Cette fonction permet d'envoyer un signal spécifique à un groupe de processus de console.
GetAce | BOOL GetAce( PACL pAcl, DWORD dwAceIndex, LPVOID *pAce ); | advapi32.dll | Cette fonction permet de demander un pointeur sur une structure d'une entrée de contrôle d'accès (ACE) d'une liste de contrôle d'accès (ACL).
GetAclInformation | BOOL GetAclInformation( PACL pAcl, LPVOID pAclInformation, DWORD nAclInformationLength, ACL_INFORMATION_CLASS dwAclInformationClass ); | advapi32.dll | Cette fonction permet de demander les informations concernant une liste de contrôle d'accès (ACL).
GetACP | UINT GetACP(VOID); | kernel32.dll | Cette fonction permet de demander l'identificateur de code de page ANSI courant pour le système d'exploitation.
GetActiveWindow | HWND GetActiveWindow(VOID); | user32.dll | Cette fonction permet de demander le Handle de la fenêtre active.
GetArcDirection | int GetArcDirection(HDC hdc); | gdi32.dll | Cette fonction permet de demander la direction de l'arc courant pour le périphérique de contexte spécifié.
GetAspectRatioFilterEx | BOOL GetAspectRatioFilterEx(HDC hdc, LPSIZE lpAspectRatio); | gdi32.dll | Cette fonction permet de demander l'ajustement de filtre d'aspect ratio courant.
GetAsyncKeyState | SHORT GetAsyncKeyState(int vKey); | user32.dll | Cette fonction permet de retourner l'état de la touche spécifié.
GetAtomNameA | UINT GetAtomNameA( ATOM nAtom, LPTSTR lpBuffer, int nSize ); | kernel32.dll | Cette fonction permet de demander une copie de chaine de caractères associé avec un Atom local spécifié.
GetAtomNameW | UINT GetAtomNameW( ATOM atom, LPWSTR buffer, INT count ); | kernel32.dll | Cette fonction permet de demander une copie de chaine de caractères associé avec un Atom local spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetBinaryTypeA | BOOL GetBinaryTypeA( LPCTSTR lpApplicationName, LPDWORD lpBinaryType ); | kernel32.dll | Cette fonction permet de demander si le fichier est exécutable, et s'il l'est, quel type de fichier exécutable s'agit-il.
GetBinaryTypeW | BOOL GetBinaryTypeW( LPCWSTR lpApplicationName, LPDWORD lpBinaryType ); | kernel32.dll | Cette fonction permet de demander si le fichier est exécutable, et s'il l'est, quel type de fichier exécutable s'agit-il avec des paramètres de chaine de caractères étendue (Unicode).
GetBitmapBits | LONG GetBitmapBits(HBITMAP hbmp, LONG cbBuffer, LPVOID lpvBits); | gdi32.dll | Cette fonction permet de copier les bits du Bitmap spécifié dans un tampon.
GetBitmapDimensionEx | BOOL GetBitmapDimensionEx(HBITMAP hBitmap, LPSIZE lpDimension); | gdi32.dll | Cette fonction permet de demander les dimensions d'un Bitmap.
GetBkColor | COLORREF GetBkColor(HDC hdc); | gdi32.dll | Cette fonction permet de retourner la couleur d'arrière-plan courante.
GetBkMode | int GetBkMode(HDC hdc); | gdi32.dll | Cette fonction permet de demander le mode mixte d'arrière-plan pour le contexte de périphérique spécifié.
GetBoundsRect | UINT GetBoundsRect(HDC hdc, LPRECT lprcBounds, UINT flags); | gdi32.dll | Cette fonction permet de retourner l'accumulation d'informations de limite rectangulaire d'un périphérique de contexte.
GetBrushOrgEx | BOOL GetBrushOrgEx(HDC hdc, LPPOINT lppt); | gdi32.dll | Cette fonction permet de demander l'origine de la brosse courante dans un périphérique de contexte courant.
GetCapture | HWND GetCapture(VOID); | user32.dll | Cette fonction permet de demander le Handle de la fenêtre où la souris est capturé.
GetCaretBlinkTime | UINT GetCaretBlinkTime(VOID); | user32.dll | Cette fonction permet de demander le taux de clignotement du caret.
GetCaretPos | BOOL GetCaretPos(LPPOINT lpPoint); | user32.dll | Cette fonction permet de demander la position courante d'un caret.
GetCharABCWidthsA | BOOL GetCharABCWidthsA(HDC hdc, UINT uFirstChar, UINT uLastChar, LPABC lpabc); | gdi32.dll | Cette fonction permet de demander la largeur, dans son unité de mesure logique, de caractères consécutifs dans une rangée données d'une police de caractères TrueType courant.
GetCharABCWidthsW | BOOL GetCharABCWidthsW(HDC hdc, UINT firstChar, UINT lastChar, LPABC abc); | gdi32.dll | Cette fonction permet de demander la largeur, dans son unité de mesure logique, de caractères consécutifs dans une rangée données d'une police de caractères TrueType courant avec des paramètres de chaines de caractères étendues (Unicode).
GetCharABCWidthsFloatA | BOOL GetCharABCWidthsFloatA(HDC hdc, UINT iFirstChar, UINT iLastChar, LPABCFLOAT lpABCF); | gdi32.dll | Cette fonction permet de demander la largeur, dans son unité de mesure logique, de caractères consécutifs dans une rangée données d'une police de caractères courant.
GetCharABCWidthsFloatW | BOOL GetCharABCWidthsFloatW(HDC hdc, UINT first, UINT last, LPABCFLOAT abcf); | gdi32.dll | Cette fonction permet de demander la largeur, dans son unité de mesure logique, de caractères consécutifs dans une rangée données d'une police de caractères courant avec des paramètres de chaine de caractères étendues (Unicode).
GetCharWidth32A | BOOL GetCharWidth32A(HDC hdc, UINT iFirstChar, UINT iLastChar, LPINT lpBuffer); | gdi32.dll | Cette fonction permet demander la largeur, dans des coordonnées logiques, de caractères consécutifs dans une rangée spécifié d'une police de caractères courante.
GetCharWidth32W | BOOL GetCharWidth32W(HDC hdc, UINT iFirstChar, UINT iLastChar, LPINT lpBuffer); | gdi32.dll | Cette fonction permet demander la largeur, dans des coordonnées logiques, de caractères consécutifs dans une rangée spécifié d'une police de caractères courante avec des paramètres de chaine de caractères étendues (Unicode).
GetCharWidthA | BOOL GetCharWidthA(HDC hdc, UINT iFirstChar, UINT iLastChar, LPINT lpBuffer); | gdi32.dll | Cette fonction permet demander la largeur, dans des coordonnées logiques, de caractères consécutifs dans une rangée spécifié d'une police de caractères courante dans un contexte de compatibilité avec les fonctions 16 bits.
GetCharWidthW | BOOL GetCharWidthW(HDC hdc, UINT iFirstChar, UINT iLastChar, LPINT lpBuffer); | gdi32.dll | Cette fonction permet demander la largeur, dans des coordonnées logiques, de caractères consécutifs dans une rangée spécifié d'une police de caractères courante dans un contexte de compatibilité avec les fonctions 16 bits avec des paramètres de chaine de caractères étendues (Unicode).
GetCharWidthFloatA | BOOL GetCharWidthFloatA(HDC hdc, UINT iFirstChar, UINT iLastChar, PFLOAT pxBuffer); | gdi32.dll | Cette fonction permet de demander la largeur fractionné de caractères consécutifs dans une rangée spécifié de la police de caractères courante.
GetCharWidthFloatW | BOOL GetCharWidthFloatW(HDC hdc, UINT iFirstChar, UINT iLastChar, PFLOAT pxBuffer); | gdi32.dll | Cette fonction permet de demander la largeur fractionné de caractères consécutifs dans une rangée spécifié de la police de caractères courante avec des paramètres de chaine de caractères étendues (Unicode).
GetCharacterPlacementA | DWORD GetCharacterPlacementA(HDC hdc, LPCTSTR lpString, int nCount, int nMaxExtent, LPGCP_RESULTS *lpResults, DWORD dwFlags); | gdi32.dll | Cette fonction permet de demander les informations à propos d'une chaine de caractères, comme la largeur des caractères, le positionnement des carets, l'ordre dans la chaine de caractères et la rendue du glyphe.
GetCharacterPlacementW | DWORD GetCharacterPlacementW(HDC hdc, LPCWSTR lpString, int uCount, int nMaxExtent, GCP_RESULTSW* lpResults, DWORD dwFlags); | gdi32.dll | Cette fonction permet de demander les informations à propos d'une chaine de caractères, comme la largeur des caractères, le positionnement des carets, l'ordre dans la chaine de caractères et la rendue du glyphe avec des paramètres de chaine de caractères étendues (Unicode).
GetClassInfoA | BOOL GetClassInfoA(HINSTANCE hInstance, LPCTSTR lpClassName, LPWNDCLASS lpWndClass); | user32.dll | Cette fonction permet de demander les informations à propos d'une classe de fenêtre.
GetClassInfoExA | BOOL GetClassInfoExA(HINSTANCE hinst, LPCTSTR lpszClass, LPWNDCLASSEX lpwcx); | user32.dll | Cette fonction permet de demander les informations à propos d'une classe de fenêtre en incluant le Handle du petit icône associé avec la fenêtre de classe.
GetClassInfoExW | BOOL GetClassInfoExW(HINSTANCE hinst, LPCWSTR lpszClass, LPWNDCLASSEXW lpwcx); | user32.dll | Cette fonction permet de demander les informations à propos d'une classe de fenêtre en incluant le Handle du petit icône associé avec la fenêtre de classe avec des paramètres de chaine de caractères étendues (Unicode).
GetClassInfoW | BOOL GetClassInfoW(HINSTANCE hInstance, LPCTSTR lpClassName, LPWNDCLASS lpWndClass); | user32.dll | Cette fonction permet de demander les informations à propos d'une classe de fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
GetClassLongA | DWORD GetClassLongA(HWND hWnd,int nIndex); | user32.dll | Cette fonction permet de demander des valeurs d'informations en 32 bits sur une classe.
GetClassLongW | DWORD GetClassLongW(HWND hWnd, int nIndex); | user32.dll | Cette fonction permet de demander des valeurs d'informations en 32 bits sur une classe avec des paramètres de chaine de caractères étendues (Unicode).
GetClassNameA | int GetClassNameA(HWND hWnd,LPTSTR lpClassName,int nMaxCount); | user32.dll | Cette fonction permet de demander le nom d'une classe.
GetClassNameW | int GetClassNameW(HWND hWnd, LPWSTR lpClassName, int nMaxCount); | user32.dll | Cette fonction permet de demander le nom d'une classe avec des paramètres de chaine de caractères étendues (Unicode).
GetClassWord | WORD GetClassWord(HWND hWnd, int nIndex); | user32.dll | Cette fonction permet de demander une valeur 16 bits de déplacement spécifié dans une classe de mémoire supplémentaire pour la classe de fenêtre à laquelle la fenêtre appartient.
GetClientRect | BOOL GetClientRect(HWND hWnd, LPRECT lpRect); | user32.dll | Cette fonction permet de demander les coordonnées client de la région de fenêtre client.
GetClipboardData | HANDLE GetClipboardData(UINT uFormat); | user32.dll | Cette fonction permet de demander le Handle du presse-papier de données courant avec le format spécifié.
GetClipboardFormatNameA | int GetClipboardFormatNameA(UINT format, LPTSTR lpszFormatName, int cchMaxCount); | user32.dll | Cette fonction permet de demander le nom de format de presse-papier enregistré.
GetClipboardFormatNameW | int GetClipboardFormatNameW(UINT format, LPWSTR lpszFormatName, int cchMaxCount); | user32.dll | Cette fonction permet de demander le nom de format de presse-papier enregistré avec des paramètres de chaine de caractères étendues (Unicode).
GetClipboardOwner | HWND GetClipboardOwner(VOID); | user32.dll | Cette fonction permet de demander le Handle de la fenêtre associé à un presse-papier.
GetClipboardViewer | HWND GetClipboardViewer(VOID); | user32.dll | Cette fonction permet de demander le Handle de la première fenêtre d'une chaine visualisation de presse-papier.
GetClipBox | int GetClipBox(HDC hdc, LPRECT lprc); | gdi32.dll | Cette fonction permet de demander les dimensions d'un petit rectangle complètement contenu dans la région de clip courant.
GetClipCursor | BOOL GetClipCursor(LPRECT lpRect); | user32.dll | Cette fonction permet de demander les coordonnées de la fenêtre limitant le déplacement du pointeur de la souris.
GetClipRgn | int GetClipRgn(HDC hdc, HRGN hrgn); | gdi32.dll | Cette fonction permet de demander le Handle d'une région de clip affecté par un périphérique de contexte spécifié.
GetColorAdjustment | BOOL GetColorAdjustment(HDC hdc, LPCOLORADJUSTMENT lpca); | gdi32.dll | Cette fonction permet de demander les valeurs de couleurs d'ajustement pour le contexte de périphérique spécifié.
GetColorSpace | HANDLE GetColorSpace(HDC hdc); | gdi32.dll | Cette fonction permet de demander le Handle courant de l'espace de couleur logique du périphérique de contexte spécifié.
GetCommandLineA | LPTSTR GetCommandLineA(void); | kernel32.dll | Cette fonction permet de retourner la ligne de commande de l'application courante.
GetCommandLineW | LPWSTR GetCommandLineW(VOID); | kernel32.dll | Cette fonction permet de retourner la ligne de commande de l'application courante avec des paramètres de chaine de caractères étendue (Unicode).
GetCommConfig | BOOL GetCommConfig( HANDLE hCommDev, LPCOMMCONFIG lpCC, LPDWORD lpdwSize ); | kernel32.dll | Cette fonction permet de demander la configuration courante pour un périphérique de communication.
GetCommMask | BOOL GetCommMask( HANDLE hFile, LPDWORD lpEvtMask ); | kernel32.dll | Cette fonction permet de demander la valeur d'un masque d'événement pour un périphérique de communication spécifié.
GetCommModemStatus | BOOL GetCommModemStatus( HANDLE hFile, LPDWORD lpModemStat ); | kernel32.dll | Cette fonction permet de demander les valeurs du registre de contrôle du modem.
GetCommProperties | BOOL GetCommProperties( HANDLE hFile, LPCOMMPROP lpCommProp ); | kernel32.dll | Cette fonction permet de remplir le tampon avec des informations concernant les propriétés de communications pour un périphérique de communication spécifié.
GetCommState | BOOL GetCommState( HANDLE hFile, LPDCB lpDCB ); | kernel32.dll | Cette fonction permet de remplir un bloc de contrôle de périphérique (une structure DCB) avec les ajustements de contrôle courant pour un périphérique de communication spécifié.
GetCommTimeouts | BOOL GetCommTimeouts( HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts ); | kernel32.dll | Cette fonction permet de demander les paramètres de dépassement de délai pour toutes les opérations de lecture ou d'écriture pour un périphérique de communication spécifié.
GetCompressedFileSizeA | DWORD GetCompressedFileSizeA( LPCTSTR lpFileName, LPDWORD lpFileSizeHigh ); | kernel32.dll | Cette fonction permet de demander le nombre d'octets d'une unité d'entreposage actuellement utilisé pour un fichier spécifié.
GetCompressedFileSizeW | DWORD GetCompressedFileSizeW( LPCWSTR name, LPDWORD size_high ); | kernel32.dll | Cette fonction permet de demander le nombre d'octets d'une unité d'entreposage actuellement utilisé pour un fichier spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetComputerNameA | BOOL GetComputerNameA(LPTSTR lpBuffer,LPDWORD nSize); | kernel32.dll | Cette fonction permet de demander le nom de l'ordinateur sous forme d'une chaine de caractères ASCIZ.
GetComputerNameW | BOOL GetComputerNameW( LPWSTR lpBuffer, PDWORD nSize ); | kernel32.dll | Cette fonction permet de demander le nom de l'ordinateur sous forme d'une chaine de caractères ASCIZ avec des paramètres de chaine de caractères étendue (Unicode).
GetComputerNameExA | BOOL GetComputerNameExA(COMPUTER_NAME_FORMAT NameType, LPTSTR lpBuffer, LPDWORD lpnSize); | kernel32.dll | Cette fonction permet de retourner la nom NetBIOS ou DNS de l'ordinateur local.
GetComputerObjectNameA | BOOLEAN GetComputerObjectNameA( EXTENDED_NAME_FORMAT NameFormat, LPTSTR lpNameBuffer, PULONG lpnSize ); | secur32.dll | Cette fonction permet de retourner le nom de l'ordinateur dans un format spécifié.
GetConsoleCursorInfo | BOOL GetConsoleCursorInfo( HANDLE hConsoleOutput, PCONSOLE_CURSOR_INFO lpConsoleCursorInfo ); | kernel32.dll | Cette fonction permet de demander des informations à propos de la taille et de la visibilité du curseur pour le tampon de console écran spécifié.
GetConsoleCP | UINT GetConsoleCP(VOID); | kernel32.dll | Cette fonction permet de demander l'identité de code de page utilisé pour la console associé avec le processus appelant.
GetConsoleMode | BOOL GetConsoleMode( HANDLE hConsoleHandle, LPDWORD lpMode ); | kernel32.dll | Cette fonction permet de demander le mode d'entrée courant pour le tampon d'entrée de la console ou le mode de sortie courant pour le tampon d'écran de la console.
GetConsoleOutputCP | UINT GetConsoleOutputCP(VOID); | kernel32.dll | Cette fonction permet de retourner l'identité de code de page de sortie utilisé par la console associé avec le processus appelant.
GetConsoleScreenBufferInfo | BOOL GetConsoleScreenBufferInfo( HANDLE hConsoleOutput, PCONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo ); | kernel32.dll | Cette fonction permet de demander les informations à propos du tampon d'écran de la console spécifié.
GetConsoleTitleA | DWORD GetConsoleTitleA(LPSTR title, DWORD size); | kernel32.dll | Cette fonction permet de retourner le nom de la console courante.
GetConsoleTitleW | DWORD GetConsoleTitleW( LPWSTR title, DWORD size ); | kernel32.dll | Cette fonction permet de retourner le nom de la console courante avec des paramètres de chaine de caractères étendue (Unicode).
GetCPInfo | BOOL GetCPInfo(UINT CodePage, LPCPINFO lpCPInfo); | kernel32.dll | Cette fonction permet de retourner des informations sur le code de page spécifié.
GetCurrencyFormatA | int GetCurrencyFormatA( LCID Locale, DWORD dwFlags, LPCTSTR lpValue, CONST CURRENCYFMT * lpFormat, LPTSTR lpCurrencyStr, int cchCurrency ); | kernel32.dll | Cette fonction permet de demander la chaine de caractères du format d'un nombre avec une chaine de caractères de monnaie pour une localité spécifié.
GetCurrencyFormatW | INT GetCurrencyFormatW( LCID lcid, DWORD dwFlags, LPCWSTR lpszValue, const CURRENCYFMTW* lpFormat, LPWSTR lpCurrencyStr, int cchOut ); | kernel32.dll | Cette fonction permet de demander la chaine de caractères du format d'un nombre avec une chaine de caractères de monnaie pour une localité spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetCurrentDirectoryA | DWORD GetCurrentDirectoryA( DWORD nBufferLength, LPTSTR lpBuffer); | kernel32.dll | Cette fonction permet de demander le répertoire courant du processus courant.
GetCurrentDirectoryW | DWORD GetCurrentDirectoryW( DWORD nBufferLength, LPWSTR lpBuffer ); | kernel32.dll | Cette fonction permet de demander le répertoire courant du processus courant avec des paramètres de chaine de caractères étendue (Unicode).
GetCurrentHwProfileA | BOOL GetCurrentHwProfileA( LPHW_PROFILE_INFOA pInfo ); | advapi32.dll | Cette fonction pemret de demander le profile matériel courant.
GetCurrentHwProfileW | BOOL GetCurrentHwProfileW( LPHW_PROFILE_INFOW pInfo ); | advapi32.dll | Cette fonction pemret de demander le profile matériel courant avec des paramètres de chaine de caractères étendue (Unicode).
GetCurrentObject | HGDIOBJ GetCurrentObject(HDC hdc, UINT uObjectType); | gdi32.dll | Cette fonction permet de retourner l'objet sélectionné courant du type spécifié.
GetCurrentPositionEx | BOOL GetCurrentPositionEx(HDC hdc, LPPOINT lpPoint); | gdi32.dll | Cette fonction permet de demander la position courante dans des coordonnées logiques.
GetCurrentProcess | HANDLE WINAPI GetCurrentProcess(void); | kernel32.dll | Cette fonction permet de retourner le Handle du processus courant.
GetCurrentProcessId | DWORD GetCurrentProcessId(VOID); | kernel32.dll | Cette fonction permet de demander l'identificateur de processus courant
GetCurrentProcessorNumber | DWORD GetCurrentProcessorNumber(void); | kernel32.dll | Cette fonction permet de demander le nombre de processeur du processus courant lors de l'appel d'une fonction
GetCurrentThread | HANDLE WINAPI GetCurrentThread(void); | kernel32.dll | Cette fonction permet de retourner le Handle du processus courant.
GetCurrentThreadId | DWORD WINAPI GetCurrentThreadId(void); | kernel32.dll | Cette fonction permet de retourner l'identificateur du processus courant.
GetCursor | HCURSOR GetCursor(VOID); | user32.dll | Cette fonction permet de demander le Handle du curseur courant.
GetCursorPos | BOOL GetCursorPos(LPPOINT lpPoint); | user32.dll | Cette fonction permet de demander la position du pointeur de souris à l'écran
GetDateFormatA | int GetDateFormatA( LCID Locale, DWORD dwFlags, CONST SYSTEMTIME * lpDate, LPCTSTR lpFormat, LPTSTR lpDateStr, int cchDate ); | kernel32.dll | Cette fonction permet de demander la date dans une chaine de caractères ASCIZ selon le format spécifié.
GetDateFormatW | int GetDateFormatW( LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime, LPCWSTR lpFormat, LPWSTR lpDateStr, int cchOut ); | kernel32.dll | Cette fonction permet de demander la date dans une chaine de caractères ASCIZ selon le format spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetDC | HDC GetDC(HWND hWnd); | user32.dll | Cette fonction permet de demander le contexte d'objet d'une fenêtre d'application.
GetDCEx | HDC GetDCEx(HWND hWnd, HRGN hrgnClip, DWORD flags); | user32.dll | Cette fonction permet de demander le Handle du périphérique de contexte pour la fenêtre spécifié.
GetDCOrgEx | BOOL GetDCOrgEx(HDC hdc, LPPOINT lpPoint); | gdi32.dll | Cette fonction permet de demander l'origine de translation final d'un périphérique de contexte spécifié.
GetDefaultCommConfigA | BOOL GetDefaultCommConfigA( LPCSTR lpszName, LPCOMMCONFIG lpCC, LPDWORD lpdwSize ); | kernel32.dll | Cette fonction permet de demander la configuration par défaut d'un périphérique de communication.
GetDefaultCommConfigW | BOOL GetDefaultCommConfigW( LPCWSTR lpszName, LPCOMMCONFIG lpCC, LPDWORD lpdwSize ); | kernel32.dll | Cette fonction permet de demander la configuration par défaut d'un périphérique de communication avec des paramètres de chaine de caractères étendue (Unicode).
GetDesktopWindow | HWND GetDesktopWindow(VOID); | user32.dll | Cette fonction permet de demander le Handle attribué à la fenêtre du bureau.
GetDeviceCaps | int GetDeviceCaps(HDC hdc, int nIndex); | gdi32.dll | Cette fonction permet de demander les informations d'un périphérique spécifié à propos du périphérique d'affichage.
GetDeviceGammaRamp | BOOL GetDeviceGammaRamp(HDC hdc, LPVOID lpRamp); | gdi32.dll | Cette fonction permet de demander la rampe gamma d'un panneau d'affichage de couleur directe.
GetDialogBaseUnits | LONG GetDialogBaseUnits(VOID); | user32.dll | Cette fonction permet de retourner l'unité de base de la boite de dialogue qu'utilise Windows quand il créer une boite de dialogue.
GetDIBColorTable | UINT GetDIBColorTable(HDC hdc, UINT uStartIndex, UINT cEntries, RGBQUAD *pColors); | gdi32.dll | Cette fonction permet de demander les valeurs de couleurs RVB (rouge, vert, bleu) d'une rangée d'entrées d'une table de couleurs d'une section DIB Bitmap étant actuellement sélectionné dans un périphérique de contexte spécifié.
GetDIBits | int GetDIBits(HDC hdc, HBITMAP hbmp, UINT uStartScan, UINT cScanLines, LPVOID lpvBits, LPBITMAPINFO lpbi, UINT uUsage); | gdi32.dll | Cette fonction permet de demander les bits d'un Bitmap compatible et copie celui-ci dans un tampon DIB en utilisant le format spécifié.
GetDiskFreeSpaceA | BOOL GetDiskFreeSpaceA( LPCTSTR lpRootPathName, LPDWORD lpSectorsPerCluster, LPDWORD lpBytesPerSector, LPDWORD lpNumberOfFreeClusters, LPDWORD lpTotalNumberOfClusters ); | kernel32.dll | Cette fonction permet de demander l'espace de libre sur une unité disque.
GetDiskFreeSpaceExA | BOOL GetDiskFreeSpaceExA( LPCSTR root, PULARGE_INTEGER avail, PULARGE_INTEGER total, PULARGE_INTEGER totalfree ); | kernel32.dll | Cette fonction permet de demander des informations global sur l'espace de libre sur une unité disque.
GetDiskFreeSpaceExW | BOOL GetDiskFreeSpaceExW( LPCWSTR root, PULARGE_INTEGER avail, PULARGE_INTEGER total, PULARGE_INTEGER totalfree ); | kernel32.dll | Cette fonction permet de demander des informations global sur l'espace de libre sur une unité disque avec des paramètres de chaine de caractères étendue (Unicode).
GetDiskFreeSpaceW | BOOL GetDiskFreeSpaceW( LPCWSTR lpRootPathName, PDWORD lpSectorsPerCluster, PDWORD lpBytesPerSector, PDWORD lpNumberOfFreeClusters, PDWORD lpTotalNumberOfClusters ); | kernel32.dll | Cette fonction permet de demander l'espace de libre sur une unité disque avec des paramètres de chaine de caractères étendue (Unicode).
GetDlgCtrlID | int GetDlgCtrlID(HWND hwndCtl); | user32.dll | Cette fonction permet de retourner le Handle de la fenêtre enfant.
GetDlgItem | HWND GetDlgItem(HWND hDlg, int nIDDlgItem); | user32.dll | Cette fonction permet de demander le Handle d'un contrôle étant données par la boite de dialogue.
GetDlgItemInt | UINT GetDlgItemInt(HWND hDlg, int nIDDlgItem, BOOL* lpTranslated, BOOL bSigned); | user32.dll | Cette fonction permet d'effectuer la translation du texte d'un contrôle d'une boite de dialogue en une valeur entière.
GetDlgItemTextA | UINT GetDlgItemTextA(HWND hDlg, int nIDDlgItem, LPTSTR lpString, int nMaxCount); | user32.dll | Cette fonction permet de demander le titre ou le texte associé avec un contrôle de boite de dialogue.
GetDlgItemTextW | UINT GetDlgItemTextW(HWND hDlg, int nIDDlgItem, LPWSTR lpString, int nMaxCount); | user32.dll | Cette fonction permet de demander le titre ou le texte associé avec un contrôle de boite de dialogue avec des paramètres de chaine de caractères étendues (Unicode).
GetDoubleClickTime | UINT GetDoubleClickTime(VOID); | user32.dll | Cette fonction permet de demander le temps maximal autorisé pour que des clics sur les boutons de la souris soit considéré comme un double-clic.
GetDriveTypeA | UINT GetDriveTypeA(LPCTSTR lpRootPathName); | kernel32.dll | Cette fonction permet de demander le type d'unité disque.
GetDriveTypeW | UINT GetDriveTypeW(LPCWSTR root); | kernel32.dll | Cette fonction permet de demander le type d'unité disque avec des paramètres de chaine de caractères étendue (Unicode).
GetEnhMetaFileA | HENHMETAFILE GetEnhMetaFileA(LPCTSTR lpszMetaFile); | gdi32.dll | Cette fonction permet de créer un Handle étant identifié par un fichier méta de format étendue entreposé dans le fichier spécifié.
GetEnhMetaFileW | HENHMETAFILE GetEnhMetaFileW(LPCWSTR lpszMetaFile); | gdi32.dll | Cette fonction permet de créer un Handle étant identifié par un fichier méta de format étendue entreposé dans le fichier spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetEnhMetaFileBits | UINT GetEnhMetaFileBits(HENHMETAFILE hemf, UINT cbBuffer, LPBYTE lpbBuffer); | gdi32.dll | Cette fonction permet de demander le contenu d'un fichier méta spécifié de format étendue et le copie dans un tampon.
GetEnhMetaFileDescriptionA | UINT GetEnhMetaFileDescriptionA(HENHMETAFILE hemf, UINT cchBuffer, LPTSTR lpszDescription); | gdi32.dll | Cette fonction permet de demander une description texte optionnel d'un fichier méta de format étendue et copie la chaine de caractères dans un tampon spécifié.
GetEnhMetaFileDescriptionW | UINT GetEnhMetaFileDescriptionW(HENHMETAFILE hmf, UINT size, LPWSTR buf); | gdi32.dll | Cette fonction permet de demander une description texte optionnel d'un fichier méta de format étendue et copie la chaine de caractères dans un tampon spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetEnhMetaFileHeader | UINT GetEnhMetaFileHeader(HENHMETAFILE hemf, UINT cbBuffer, LPENHMETAHEADER lpemh); | gdi32.dll | Cette fonction permet de demander l'enregistrement contenu dans l'entête du fichier méta spécifié de format étendue.
GetEnhMetaFilePaletteEntries | UINT GetEnhMetaFilePaletteEntries(HENHMETAFILE hemf, UINT cEntries, LPPALETTEENTRY lppe); | gdi32.dll | Cette fonction permet de demander les entrées de palette optionnel pour le fichier méta étendue spécifié.
GetEnhMetaFilePixelFormat | UINT GetEnhMetaFilePixelFormat(HENHMETAFILE hemf, DWORD cbBuffer, const PIXELFORMATDESCRIPTOR *ppfd); | gdi32.dll | Cette fonction permet de demander les informations de format de pixel pour un fichier méta étendue.
GetEnvironmentStringsA | LPTCH GetEnvironmentStringsA(void); | kernel32.dll | Cette fonction permet de demander les variables d'environnements du processus courant.
GetEnvironmentStringsW | LPWSTR GetEnvironmentStringsW(void); | kernel32.dll | Cette fonction permet de demander les variables d'environnements du processus courant avec des paramètres de chaine de caractères étendue (Unicode).
GetEnvironmentVariableA | DWORD GetEnvironmentVariableA(LPCTSTR lpName,LPTSTR lpBuffer,DWORD nSize); | kernel32.dll | Cette fonction permet de demander la valeur d'une variable d'environnement du système d'exploitation.
GetEnvironmentVariableW | DWORD GetEnvironmentVariableW( LPCWSTR lpName, LPWSTR lpBuffer, DWORD nSize ); | kernel32.dll | Cette fonction permet de demander la valeur d'une variable d'environnement du système d'exploitation avec des paramètres de chaine de caractères étendue (Unicode).
GetExitCodeProcess | BOOL GetExitCodeProcess( HANDLE hProcess, LPDWORD lpExitCode); | kernel32.dll | Cette fonction permet de demander le code d'état de terminaison d'un processus spécifié.
GetExitCodeThread | BOOL GetExitCodeThread(HANDLE hThread,LPDWORD lpExitCode); | kernel32.dll | Cette fonction permet de demander le code d'état de terminaison d'un processus léger (Thread) spécifié.
GetFileAttributesA | DWORD GetFileAttributesA(LPCTSTR lpFileName); | kernel32.dll | Cette fonction permet de demander les attributs d'un fichier ou d'un répertoire.
GetFileAttributesExA | BOOL GetFileAttributesExA( LPCSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, PVOID lpFileInformation ); | kernel32.dll | Cette fonction permet de demander les informations étendues d'attributs d'un fichier ou d'un répertoire.
GetFileAttributesExW | BOOL GetFileAttributesExW( LPCWSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, PVOID lpFileInformation ); | kernel32.dll | Cette fonction permet de demander les informations étendues d'attributs d'un fichier ou d'un répertoire avec des paramètres de chaine de caractères étendue (Unicode).
GetFileAttributesW | DWORD GetFileAttributesW(LPCWSTR lpFileName); | kernel32.dll | Cette fonction permet de demander les attributs d'un fichier ou d'un répertoire avec des paramètres de chaine de caractères étendue (Unicode).
GetFileInformationByHandle | BOOL GetFileInformationByHandle( HANDLE hFile, LPBY_HANDLE_FILE_INFORMATION lpFileInformation, ); | kernel32.dll | Cette fonction permet de demander les informations associé au fichier spécifié.
GetFileSecurityA | BOOL GetFileSecurityA( LPCTSTR lpFileName, SECURITY_INFORMATION RequestedInformation, PSECURITY_DESCRIPTOR pSecurityDescriptor, DWORD nLength, LPDWORD lpnLengthNeeded ); | advapi32.dll | Cette fonction permet de demander les informations spécifié sur la sécurité d'un fichier ou d'un répertoire.
GetFileSecurityW | BOOL GetFileSecurityW( LPCWSTR lpFileName, SECURITY_INFORMATION RequestedInformation, PSECURITY_DESCRIPTOR pSecurityDescriptor, DWORD nLength, LPDWORD lpnLengthNeeded ); | advapi32.dll | Cette fonction permet de demander les informations spécifié sur la sécurité d'un fichier ou d'un répertoire avec des paramètres de chaine de caractères étendue (Unicode).
GetFileSize | DWORD GetFileSize( HANDLE hFile, LPDWORD lpFileSizeHigh, ); | kernel32.dll | Cette fonction permet de demander la taille, en octets, d'un fichier spécifié.
GetFileTime | BOOL GetFileTime(HANDLE hFile,LPFILETIME lpCreationTime,LPFILETIME lpLastAccessTime,LPFILETIME lpLastWriteTime); | kernel32.dll | Cette fonction permet de demander la date et l'heure d'un fichier de sa création, de sa dernière modification ou de son dernière accès.
GetFileType | DWORD GetFileType(HANDLE hFile); | kernel32.dll | Cette fonction permet de retourner le type d'un fichier spécifié.
GetFileVersionInfoSizeA | DWORD GetFileVersionInfoSizeA(LPTSTR lptstrFilename,LPDWORD lpdwHandle); | version.dll | Cette fonction permet de demander la taille de l'entête d'information de version du fichier spécifié.
GetFocus | HWND GetFocus(VOID); | user32.dll | Cette fonction permet de demander le focus d'entrée de la fenêtre spécifié.
GetFontData | DWORD GetFontData(HDC hdc, DWORD dwTable, DWORD dwOffset, LPVOID lpvBuffer, DWORD cbData); | gdi32.dll | Cette fonction permet de demander les données de police de caractères métriques pour une police de caractères TrueType.
GetFontLanguageInfo | DWORD GetFontLanguageInfo(HDC hdc); | gdi32.dll | Cette fonction permet de retourner les informations à propos de sélection de police de caractères courante dans l'affichage de contexte spécifié.
GetForegroundWindow | HWND GetForegroundWindow(VOID); | user32.dll | Cette fonction permet de demander le Handle de la fenêtre d'application courante.
GetFullPathNameA | DWORD GetFullPathNameA( LPCTSTR lpFileName, DWORD nBufferLength, LPTSTR lpBuffer, LPTSTR *lpFilePart ); | kernel32.dll | Cette fonction permet de demander le chemin et le nom de fichier complet pour un fichier spécifié.
GetFullPathNameW | DWORD GetFullPathNameW( LPCWSTR lpFileName, DWORD nBufferLength, LPWSTR lpBuffer, LPWSTR *lpFilePart ); | kernel32.dll | Cette fonction permet de demander le chemin et le nom de fichier complet pour un fichier spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetGlyphOutlineA | DWORD GetGlyphOutlineA(HDC hdc, UINT uChar, UINT uFormat, LPGLYPHMETRICS lpgm, DWORD cbBuffer, LPVOID lpvBuffer, CONST MAT2 * lpmat2); | gdi32.dll | Cette fonction permet de demander ligne de limite ou du Bitmap d'un caractère d'une police de caractères TrueType étant sélectionné dans le périphérique de contexte spécifié.
GetGlyphOutlineW | DWORD GetGlyphOutlineW(HDC hdc, UINT uChar, UINT uFormat, LPGLYPHMETRICS lpgm, DWORD cbBuffer, LPVOID lpvBuffer, CONST MAT2 * lpmat2); | gdi32.dll | Cette fonction permet de demander ligne de limite ou du Bitmap d'un caractère d'une police de caractères TrueType étant sélectionné dans le périphérique de contexte spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetGraphicsMode | int GetGraphicsMode(HDC hdc); | gdi32.dll | Cette fonction permet de demander le mode graphique courant pour le périphérique de contexte courant.
GetGuiResources | DWORD GetGuiResources(HANDLE hProcess, DWORD uiFlags); | kernel32.dll | Cette fonction permet de demander le nombre d'identificateur de GUI (Interface graphique utilisateur) du processus courant.
GetHandleInformation | BOOL GetHandleInformation( HANDLE hObject, LPDWORD lpdwFlags ); | kernel32.dll | Cette fonction permet de demander les informations conçernant certaines propriétés d'un objet avec un identificateur Handle.
GetICMProfileA | BOOL GetICMProfileA(HDC hdc, DWORD cbName, LPTSTR lpszFilename); | gdi32.dll | Cette fonction permet de demander le nom du fichier de profile de couleurs pour le périphérique associé avec le périphérique de contexte spécifié.
GetICMProfileW | BOOL GetICMProfileW(HDC hdc, LPDWORD size, LPWSTR filename); | gdi32.dll | Cette fonction permet de demander le nom du fichier de profile de couleurs pour le périphérique associé avec le périphérique de contexte spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetIconInfo | BOOL GetIconInfo(HICON hIcon,PICONINFO piconinfo); | user32.dll | Cette fonction permet de demander les informations à propos de l'icône ou du curseur spécifié.
GetInputState | BOOL GetInputState(VOID); | user32.dll | Cette fonction permet d'indiquer si un événement de clic de souris ou de clavier est survenue dans la file d'attente du système du traitement requis.
GetIpStatistics | DWORD GetIpStatistics(PMIB_IPSTATS pStats); | iphlpapi.dll | Cette fonction permet de demander les informations de statistique sur le IP de l'ordinateur.
GetJobA | BOOL GetJobA( HANDLE hPrinter, DWORD JobId, DWORD Level, LPBYTE pJob, DWORD cbBuf, LPDWORD pcbNeeded ); | winspool.drv | Cette fonction permet de demander les données d'un travail contenu dans le tampon d'impressions de l'imprimante.
GetKBCodePage | UINT GetKBCodePage(VOID); | user32.dll | Cette fonction retourne le code de page chargé par le système d'exploitation Windows.
GetKernelObjectSecurity | BOOL GetKernelObjectSecurity( HANDLE Handle, SECURITY_INFORMATION RequestedInformation, PSECURITY_DESCRIPTOR pSecurityDescriptor, DWORD nLength, LPDWORD lpnLengthNeeded ); | advapi32.dll | Cette fonction permet de demander une copie du descripteur de sécurité protégé d'un objet du noyau du système d'exploitation.
GetKerningPairs | DWORD GetKerningPairs(HDC hdc, DWORD nNumPairs, LPKERNINGPAIR lpkrnpair); | gdi32.dll | Cette fonction permet de demander les paires de crénages de caractères pour la police de caractères courante sélectionné du périphérique de contexte spécifié.
GetKeyboardLayout | HKL GetKeyboardLayout(DWORD dwLayout); | user32.dll | Cette fonction permet de demander la couche de clavier active pour le processus léger spécifié.
GetKeyboardLayoutList | UINT GetKeyboardLayoutList(int nBuff, HKL FAR * lpList); | user32.dll | Cette fonction permet de demander le Handle de la couche de clavier correspondant à l'ensemble d'entrée courant local dans le système.
GetKeyboardLayoutNameA | BOOL GetKeyboardLayoutNameA(LPTSTR pwszKLID); | user32.dll | Cette fonction permet de demander le nom des couches de clavier active.
GetKeyboardLayoutNameW | BOOL GetKeyboardLayoutNameW(LPWSTR pwszKLID); | user32.dll | Cette fonction permet de demander le nom des couches de clavier active avec des paramètres de chaine de caractères étendues (Unicode).
GetKeyboardState | BOOL GetKeyboardState(PBYTE lpKeyState); | user32.dll | Cette fonction permet de demander l'état des 256 touches virtuel de clavier.
GetKeyboardType | int GetKeyboardType(int nTypeFlag); | user32.dll | Cette fonction permet de demander les informations à propos du clavier courant.
GetKeyNameTextA | int GetKeyNameTextA(LONG lParam, LPTSTR lpString, int nSize); | user32.dll | Cette fonction permet de demander une chaine de caractères représentant le nom d'une touche du clavier.
GetKeyNameTextW | int GetKeyNameTextW(LONG lParam, LPWSTR lpString, int nSize); | user32.dll | Cette fonction permet de demander une chaine de caractères représentant le nom d'une touche du clavier avec des paramètres de chaine de caractères étendues (Unicode).
GetKeyState | SHORT GetKeyState(int nVirtKey); | user32.dll | Cette fonction permet de demander l'état d'une touche virtuel de clavier.
GetLargestConsoleWindowSize | COORD GetLargestConsoleWindowSize( HANDLE hConsoleOutput ); | kernel32.dll | Cette fonction permet de demander la taille de la fenêtre de console la plus large possible en se basant sur la police de caractères et la taille d'affichage.
GetLastActivePopup | HWND GetLastActivePopup(HWND hWnd); | user32.dll | Cette fonction permet de demander laquelle des fenêtres du propriétaire de la fenêtre POP-UP a été récamment activé.
GetLastError | DWORD GetLastError(VOID); | kernel32.dll | Cette fonction permet de demander la dernière erreur s'étant produite dans une API.
GetLengthSid | DWORD GetLengthSid( PSID pSid ); | advapi32.dll | Cette fonction permet de retourner la longueur, en octets, d'une structure valide d'identificateur de sécurité (SID).
GetLocaleInfoA | int GetLocaleInfoA(LCID Locale,LCTYPE LCType,LPTSTR lpLCData,int cchData); | kernel32.dll | Cette fonction permet de demander des informations de nature local sur le système.
GetLocaleInfoW | INT GetLocaleInfoW( LCID lcid, LCTYPE lctype, LPWSTR buffer, INT len ); | kernel32.dll | Cette fonction permet de demander des informations de nature local sur le système avec des paramètres de chaine de caractères étendue (Unicode).
GetLocalTime | VOID GetLocalTime( LPSYSTEMTIME lpSystemTime ); | kernel32.dll | Cette fonction permet de demander la date et l'heure local du système.
GetLogColorSpaceA | BOOL GetLogColorSpaceA(HCOLORSPACE hColorSpace, LPLOGCOLORSPACE lpbuffer, DWORD nSize); | gdi32.dll | Cette fonction permet de demander les informations à propos de l'espace de couleurs logiques identifié par le Handle spécifié.
GetLogColorSpaceW | BOOL GetLogColorSpaceW(HCOLORSPACE a0, LPLOGCOLORSPACEW a1, DWORD a2); | gdi32.dll | Cette fonction permet de demander les informations à propos de l'espace de couleurs logiques identifié par le Handle spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetLogicalDrives | DWORD GetLogicalDrives(VOID); | kernel32.dll | Cette fonction permet de demander quel sont les unités de disque valide.
GetLogicalDriveStringsA | DWORD GetLogicalDriveStringsA( DWORD nBufferLength, LPTSTR lpBuffer ); | kernel32.dll | Cette fonction permet de remplir un tampon avec une chaine de caractères spécifiant une unité de disque système valide.
GetLogicalDriveStringsW | DWORD GetLogicalDriveStringsW( DWORD nBufferLength, LPWSTR lpBuffer ); | kernel32.dll | Cette fonction permet de remplir un tampon avec une chaine de caractères spécifiant une unité de disque système valide avec des paramètres de chaine de caractères étendue (Unicode).
GetLogicalProcessorInformation | BOOL GetLogicalProcessorInformation( PSYSTEM_LOGICAL_PROCESSOR_INFORMATION Buffer, PDWORD ReturnLength); | kernel32.dll | Cette fonction permet de demander des informations concernant les processeurs logiques et le matériel associé.
GetMailslotInfo | BOOL GetMailslotInfo( HANDLE hMailslot, LPDWORD lpMaxMessageSize, LPDWORD lpNextSize, LPDWORD lpMessageCount, LPDWORD lpReadTimeout ); | kernel32.dll | Cette fonction permet de demander des informations pour une slot de courriel.
GetMapMode | int GetMapMode(HDC hdc); | gdi32.dll | Cette fonction permet de demander le mode de cartographie courant.
GetMenu | HMENU GetMenu(HWND hWnd); | user32.dll | Cette fonction permet de demander le Handle du menu déroulant de la fenêtre d'application spécifié.
GetMenuCheckMarkDimensions | LONG GetMenuCheckMarkDimensions(VOID); | user32.dll | Cette fonction permet de retourner les dimensions du Bitmap marqué par défaut.
GetMenuContextHelpId | DWORD GetMenuContextHelpId(HMENU hmenu); | user32.dll | Cette fonction permet de demander l'identifiant de contexte d'aide associé avec le menu spécifié.
GetMenuDefaultItem | UINT GetMenuDefaultItem(HMENU hMenu, UINT fByPos, UINT gmdiFlags); | user32.dll | Cette fonction permet de demander l'item de menu par défaut du menu spécifié.
GetMenuItemCount | int GetMenuItemCount(HMENU hMenu); | user32.dll | Cette fonction permet de demander le nombre d'item que contient menu déroulant.
GetMenuItemID | UINT GetMenuItemID(HMENU hMenu, int nPos); | user32.dll | Cette fonction permet de demander les identificateurs d'un emplacement d'item de menu d'une position spécifié.
GetMenuItemInfoA | BOOL GetMenuItemInfoA(HMENU hMenu, UINT uItem, BOOL fByPosition, LPMENUITEMINFO lpmii); | user32.dll | Cette fonction permet de demander les informations à propos de l'item de menu.
GetMenuItemInfoW | BOOL GetMenuItemInfoW(HMENU hMenu, UINT uItem, BOOL fByPosition, LPMENUITEMINFOW lpmii); | user32.dll | Cette fonction permet de demander les informations à propos de l'item de menu avec des paramètres de chaine de caractères étendues (Unicode).
GetMenuItemRect | BOOL GetMenuItemRect(HWND hWnd, HMENU hMenu, UINT uItem, LPRECT lprcItem); | user32.dll | Cette fonction permet de demander les limites rectangulaires pour l'item de menu spécifié.
GetMenuState | UINT GetMenuState(HMENU hMenu, UINT uId, UINT uFlags); | user32.dll | Cette fonction permet de demander l'état des drapeaux associés avec l'item de menu spécifié.
GetMenuStringA | int GetMenuStringA(HMENU hMenu, UINT uIDItem, LPTSTR lpString, int nMaxCount, UINT uFlag); | user32.dll | Cette fonction permet de copier l'étiquette d'un item de menu dans un tampon.
GetMenuStringW | int GetMenuStringW(HMENU hMenu, UINT uIDItem, LPWSTR lpString, int nMaxCount, UINT uFlag); | user32.dll | Cette fonction permet de copier l'étiquette d'un item de menu dans un tampon avec des paramètres de chaine de caractères étendues (Unicode).
GetMessageA | BOOL GetMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax); | user32.dll | Cette fonction permet de demander un message d'une application de file d'attente de message dans une structure spécifié.
GetMessageW | BOOL GetMessageW(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax); | user32.dll | Cette fonction permet de demander un message d'une application de file d'attente de message dans une structure spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetMessageExtraInfo | LONG GetMessageExtraInfo(VOID); | user32.dll | Cette fonction permet de demander les informations supplémentaires associés avec le dernier message des fonctions GetMessage ou PeekMessage.
GetMessagePos | DWORD GetMessagePos(VOID); | user32.dll | Cette fonction permet de retourner une valeur long représentant une position de curseur dans une coordonnée d'écran.
GetMessageTime | LONG GetMessageTime(VOID); | user32.dll | Cette fonction permet de retourner l'heure du dernier message reçu par la fonction GetMessage.
GetMetaFileA | HMETAFILE GetMetaFileA(LPCTSTR lpszFile); | gdi32.dll | Cette fonction permet de créer un Handle identifiant le format Windows de fichier de méta données.
GetMetaFileW | HMETAFILE GetMetaFileW(LPCWSTR lpszMetaFile); | gdi32.dll | Cette fonction permet de créer un Handle identifiant le format Windows de fichier de méta données avec des paramètres de chaine de caractères étendues (Unicode).
GetMetaFileBitsEx | UINT GetMetaFileBitsEx(HMETAFILE hmf, UINT nSize, LPVOID lpvData); | gdi32.dll | Cette fonction permet de demander le contenu d'un fichier méta de format Windows et copie le contenu dans un tampon spécifié.
GetMetaRgn | int GetMetaRgn(HDC hdc, HRGN hrgn); | gdi32.dll | Cette fonction permet de demander la région méta courante pour le périphérique de contexte spécifié.
GetMiterLimit | BOOL GetMiterLimit(HDC hdc, PFLOAT peLimit); | gdi32.dll | Cette fonction permet de demander la limite mitre pour le périphérique de contexte spécifié.
GetModuleFileNameA | DWORD GetModuleFileNameA( HMODULE hModule, LPTSTR lpFilename, DWORD nSize ); | kernel32.dll | Cette fonction permet de demander un nom de fichier long ou demande le nom complet de chemin et nom de fichier pour un fichier exécutable contenu dans le module spécifié.
GetModuleFileNameW | DWORD GetModuleFileNameW ( HMODULE hModule, LPWSTR lpFileName, DWORD size ); | kernel32.dll | Cette fonction permet de demander un nom de fichier long ou demande le nom complet de chemin et nom de fichier pour un fichier exécutable contenu dans le module spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetModuleHandleA | HMODULE GetModuleHandleA( LPCTSTR lpModuleName ); | kernel32.dll | Cette fonction permet de retourner l'identificateur Handle du module spécifié si le fichier est situé dans l'espace d'adresse d'un processus appelant.
GetModuleHandleW | HMODULE GetModuleHandleW( LPCWSTR module ); | kernel32.dll | Cette fonction permet de retourner l'identificateur Handle du module spécifié si le fichier est situé dans l'espace d'adresse d'un processus appelant avec des paramètres de chaine de caractères étendue (Unicode).
GetNamedPipeHandleStateA | BOOL GetNamedPipeHandleStateA( HANDLE hNamedPipe, LPDWORD lpState, LPDWORD lpCurInstances, LPDWORD lpMaxCollectionCount, LPDWORD lpCollectDataTimeout, LPTSTR lpUserName, DWORD nMaxUserNameSize ); | kernel32.dll | Cette fonction permet de demander les informations d'état conçernant le nom du tuyau spécifié.
GetNamedPipeHandleStateW | BOOL GetNamedPipeHandleStateW( HANDLE hNamedPipe, PDWORD lpState, PDWORD lpCurInstances, PDWORD lpMaxCollectionCount, PDWORD lpCollectDataTimeout, LPWSTR lpUserName, DWORD nMaxUserNameSize ); | kernel32.dll | Cette fonction permet de demander les informations d'état conçernant le nom du tuyau spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetNamedPipeInfo | BOOL GetNamedPipeInfo( HANDLE hNamedPipe, LPDWORD lpFlags, LPDWORD lpOutBufferSize, LPDWORD lpInBufferSize, LPDWORD lpMaxInstances ); | kernel32.dll | Cette fonction permet de demander les informations conçernant le nom de tuyau spécifié.
GetNearestColor | COLORREF GetNearestColor(HDC hdc, COLORREF crColor); | gdi32.dll | Cette fonction permet de demander la couleur solide correspondant le mieux à la couleur logique.
GetNearestPaletteIndex | UINT GetNearestPaletteIndex(HPALETTE hpal, COLORREF crColor); | gdi32.dll | Cette fonction permet de demander l'index d'une entrée de palette logique correspondant le mieux à la valeur de couleur spécifié.
GetNextDlgGroupItem | HWND GetNextDlgGroupItem(HWND hDlg, HWND hCtl, BOOL bPrevious); | user32.dll | Cette fonction permet de rechercher le contrôle précédent ou suivant d'un groupe de contrôle dans une boite de dialogue.
GetNextDlgTabItem | HWND GetNextDlgTabItem(HWND hDlg, HWND hCtl, BOOL bPrevious); | user32.dll | Cette fonction permet de demander le Handle du premier contrôle d'un style WS_TABSTOP du contrôle spécifié.
GetNextWindow | HWND GetNextWindow(HWND hWnd, UINT wCmd); | user32.dll | Cette fonction permet de rechercher le Handle de la prochaine ou précédente fenêtre dans la liste du gestionnaire de fenêtre.
GetNumaAvailableMemoryNode | BOOL GetNumaAvailableMemoryNode(UCHAR Node,PULONGLONG AvailableBytes); | kernel32.dll | Cette fonction permet de demander la quantité de mémoire disponible dans le noeud spécifié.
GetNumberFormatA | int GetNumberFormatA( LCID Locale, DWORD dwFlags, LPCTSTR lpValue, CONST NUMBERFMT * lpFormat, LPTSTR lpNumberStr, int cchNumber ); | kernel32.dll | Cette fonction permet de transformer un nombre selon le format d'affichage spécifié.
GetNumberFormatW | int GetNumberFormatW( LCID lcid, DWORD dwFlags, LPCWSTR lpszValue, const NUMBERFMTW* lpFormat, LPWSTR lpNumberStr, int cchOut ); | kernel32.dll | Cette fonction permet de transformer un nombre selon le format d'affichage spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetNumberOfConsoleInputEvents | BOOL GetNumberOfConsoleInputEvents( HANDLE hConsoleInput, LPDWORD lpcNumberOfEvents ); | kernel32.dll | Cette fonction permet de demander le nombre d'enregistrement d'entrée non lue dans le tampon d'entrée de console.
GetNumberOfConsoleMouseButtons | BOOL GetNumberOfConsoleMouseButtons( LPDWORD lpNumberOfMouseButtons ); | kernel32.dll | Cette fonction permet de demander le nombre de boutons de la souris qu'utilise la console courante.
GetNumberOfEventLogRecords | BOOL GetNumberOfEventLogRecords( HANDLE hEventLog, PDWORD NumberOfRecords ); | advapi32.dll | Cette fonction permet de demander le nombre d'enregistrement de l'événement du journal de bord spécifié.
GetObjectA | int GetObjectA(HGDIOBJ hgdiobj, int cbBuffer, LPVOID lpvObject); | gdi32.dll | Cette fonction permet de remplir un tampon avec les informations définit dans la région spécifié.
GetObjectW | int GetObjectW(HGDIOBJ hgdiobj, int cbBuffer, LPVOID lpvObject); | gdi32.dll | Cette fonction permet de remplir un tampon avec les informations définit dans la région spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetObjectType | DWORD GetObjectType(HGDIOBJ h); | gdi32.dll | Cette fonction permet de demander l'identifiant de type d'objet spécifié.
GetOEMCP | UINT GetOEMCP(VOID); | kernel32.dll | Cette fonction permet de demander le code de page OEM du système d'exploitation.
GetOldestEventLogRecord | BOOL GetOldestEventLogRecord( HANDLE hEventLog, PDWORD OldestRecord ); | advapi32.dll | Cette fonction permet de demander le nombre d'enregistrement dans les anciens enregistrement du journal de bord spécifié.
GetOpenClipboardWindow | HWND GetOpenClipboardWindow(VOID); | user32.dll | Cette fonction permet de demander le Handle de la fenêtre actuellement ouverte dans le presse-papier.
GetOpenFileNameA | BOOL GetOpenFileNameA(LPOPENFILENAME lpofn); | comdlg32.dll | Cette fonction permet d'afficher une boite de dialogue offrant la possibilité de choisir un fichier.
GetOutlineTextMetricsA | UINT GetOutlineTextMetricsA(HDC hdc, UINT cbData, LPOUTLINETEXTMETRIC lpOTM); | gdi32.dll | Cette fonction permet de demander le métrique du texte pour la police de caractères TrueType.
GetOutlineTextMetricsW | UINT GetOutlineTextMetricsW(HDC hdc, UINT cbData, LPOUTLINETEXTMETRICW lpOTM); | gdi32.dll | Cette fonction permet de demander le métrique du texte pour la police de caractères TrueType avec des paramètres de chaine de caractères étendues (Unicode).
GetOverlappedResult | BOOL GetOverlappedResult( HANDLE hFile, LPOVERLAPPED lpOverlapped, LPDWORD lpNumberOfBytesTransferred, BOOL bWait ); | kernel32.dll | Cette fonction permet de retourner le résultat d'une opération de recouvrement sur un fichier, un tuyau ou un périphérique de communications spécifié
GetPaletteEntries | UINT GetPaletteEntries(HPALETTE hpal, UINT iStartIndex, UINT nEntries, LPPALETTEENTRY lppe); | gdi32.dll | Cette fonction permet de demander le rang des entrées d'une palette dans une palette logique.
GetParent | HWND GetParent(HWND hWnd); | user32.dll | Cette fonction permet de demander le Handle de la fenêtre parent spécifié.
GetPath | int GetPath(HDC hdc, LPPOINT lpPoints, LPBYTE lpTypes, int nSize); | gdi32.dll | Cette fonction permet de demander les coordonnées définit par des points de fin de lignes et une courbe de point de contrôle trouvé dans un chemin étant sélectionné dans un périphérique de contexte spécifié.
GetPixel | COLORREF GetPixel(HDC hdc,int nXPos,int nYPos); | gdi32.dll | Cette fonction permet de demander la couleur sous format RVB (RGB) d'un pixel affiché à l'écran.
GetPixelFormat | int GetPixelFormat(HDC hdc); | gdi32.dll | Cette fonction permet de demander l'index du format de pixel actuellement sélectionné dans le périphérique de contexte spécifié.
GetPolyFillMode | int GetPolyFillMode(HDC hdc); | gdi32.dll | Cette fonction permet de demander le mode de remplissage d'un polygone.
GetPrinterA | BOOL GetPrinterA( HANDLE hPrinter, DWORD Level, LPBYTE pPrinter, DWORD cbBuf, LPDWORD pcbNeeded ); | winspool.drv | Cette fonction permet de demander des informations sur une imprimante.
GetPriorityClass | DWORD GetPriorityClass( HANDLE hProcess ); | kernel32.dll | Cette fonction permet de retourner la priorité d'une classe pour un processus spécifié.
GetPriorityClipboardFormat | int GetPriorityClipboardFormat(UINT * paFormatPriorityList, int cFormats); | user32.dll | Cette fonction permet de demander le format du premier presse-papier dans la liste de données existantes du presse-papier.
GetPrivateObjectSecurity | BOOL GetPrivateObjectSecurity( PSECURITY_DESCRIPTOR ObjectDescriptor, SECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR ResultantDescriptor, DWORD DescriptorLength, PDWORD ReturnLength ); | advapi32.dll | Cette fonction permet de demander des informations de descripteur de sécurité à un serveur d'objet protégé.
GetPrivateProfileIntA | UINT GetPrivateProfileIntA( LPCTSTR lpAppName, LPCTSTR lpKeyName, INT nDefault, LPCTSTR lpFileName ); | kernel32.dll | Cette fonction permet de demander un entier associé avec une clef dans une section spécifié d'un fichier initialisé.
GetPrivateProfileIntW | UINT GetPrivateProfileIntW( LPCWSTR lpAppName, LPCWSTR lpKeyName, INT nDefault, LPCWSTR lpFileName ); | kernel32.dll | Cette fonction permet de demander un entier associé avec une clef dans une section spécifié d'un fichier initialisé avec des paramètres de chaine de caractères étendue (Unicode).
GetPrivateProfileSectionA | DWORD GetPrivateProfileSectionA( LPCTSTR lpAppName, LPTSTR lpReturnedString, DWORD nSize, LPCTSTR lpFileName ); | kernel32.dll | Cette fonction permet de demander tous les valeurs et clefs pour la section initialisé spécifié.
GetPrivateProfileSectionW | DWORD GetPrivateProfileSectionW( LPCWSTR lpAppName, LPWSTR lpReturnedString, DWORD nSize, LPCWSTR lpFileName ); | kernel32.dll | Cette fonction permet de demander tous les valeurs et clefs pour la section initialisé spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetPrivateProfileSectionNamesA | DWORD GetPrivateProfileSectionNamesA( LPTSTR lpszReturnBuffer, DWORD nSize, LPCTSTR lpFileName ); | kernel32.dll | Cette fonction permet de demander les noms de toutes les sections dans un fichier initialisé.
GetPrivateProfileSectionNamesW | DWORD GetPrivateProfileSectionNamesW( LPWSTR lpszReturnBuffer, DWORD nSize, LPCWSTR lpFileName ); | kernel32.dll | Cette fonction permet de demander les noms de toutes les sections dans un fichier initialisé avec des paramètres de chaine de caractères étendue (Unicode).
GetPrivateProfileStringA | DWORD GetPrivateProfileStringA( LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpDefault, LPTSTR lpReturnedString, DWORD nSize, LPCTSTR lpFileName ); | kernel32.dll | Cette fonction permet de demander une chaine de caractères de la section spécifié dans un fichier initialisé.
GetPrivateProfileStringW | DWORD GetPrivateProfileStringW( LPCWSTR lpAppName, LPCWSTR lpKeyName, LPCWSTR lpDefault, LPWSTR lpReturnedString, DWORD nSize, LPCWSTR lpFileName ); | kernel32.dll | Cette fonction permet de demander une chaine de caractères de la section spécifié dans un fichier initialisé avec des paramètres de chaine de caractères étendue (Unicode).
GetProcAddress | FARPROC GetProcAddress( HMODULE hModule, LPCSTR lpProcName ); | kernel32.dll | Cette fonction permet de demander l'adresse d'une fonction d'exportation de bibliothèque de liens dynamique (DLL).
GetProcessAffinityMask | BOOL GetProcessAffinityMask( HANDLE hProcess, LPDWORD lpProcessAffinityMask, LPDWORD lpSystemAffinityMask ); | kernel32.dll | Cette fonction permet de demander un masque de correspondance de processus spécifié avec le système.
GetProcessHeap | HANDLE GetProcessHeap(VOID); | kernel32.dll | Cette fonction permet de demander un identificateur Handle pour la mémoire de tas d'un processus appelant.
GetProcessHeaps | DWORD GetProcessHeaps( DWORD NumberOfHeaps, PHANDLE ProcessHeaps ); | kernel32.dll | Cette fonction permet de demander un identificateur Handle pour tous les mémoires de tas valides pour un processus appelant.
GetProcessPriorityBoost | BOOL GetProcessPriorityBoost( HANDLE hProcess, PBOOL pDisablePriorityBoost ); | kernel32.dll | Cette fonction permet de demander l'état de la priorité de contrôle d'amplificateur d'un processus spécifié.
GetProcessShutdownParameters | BOOL GetProcessShutdownParameters( LPDWORD lpdwLevel, LPDWORD lpdwFlags ); | kernel32.dll | Cette fonction permet de demander les paramètres de fermetures et d'éteindre le système d'exploitation pour un le processus appelant courant.
GetProcessTimes | BOOL GetProcessTimes( HANDLE hProcess, LPFILETIME lpCreationTime, LPFILETIME lpExitTime, LPFILETIME lpKernelTime, LPFILETIME lpUserTime ); | kernel32.dll | Cette fonction permet de demander les informations d'horloge pour le processus spécifié.
GetProcessVersion | DWORD GetProcessVersion( DWORD ProcessId ); | kernel32.dll | Cette fonction permet de demander la version majeur et mineur pour un processus spécifié en exécution.
GetProcessWindowStation | HWINSTA GetProcessWindowStation(VOID); | user32.dll | Cette fonction permet de demander le Handle de la fenêtre de station associé avec le processus appelant.
GetProcessWorkingSetSize | BOOL GetProcessWorkingSetSize( HANDLE hProcess, LPDWORD lpMinimumWorkingSetSize, LPDWORD lpMaximumWorkingSetSize ); | kernel32.dll | Cette fonction permet de demander la taille de l'espace de travail minimum et maximum pour le processus spécifié.
GetProfileIntA | UINT GetProfileIntA( LPCTSTR lpAppName, LPCTSTR lpKeyName, INT nDefault ); | kernel32.dll | Cette fonction permet de demander un entier pour le nom de clef spécifié dans une section spécifié dans un fichier «WIN.INI».
GetProfileIntW | UINT GetProfileIntW( LPCWSTR lpAppName, LPCWSTR lpKeyName, INT nDefault ); | kernel32.dll | Cette fonction permet de demander un entier pour le nom de clef spécifié dans une section spécifié dans un fichier «WIN.INI» avec des paramètres de chaine de caractères étendue (Unicode).
GetProfileSectionA | DWORD GetProfileSectionA( LPCTSTR lpAppName, LPTSTR lpReturnedString, DWORD nSize ); | kernel32.dll | Cette fonction permet de demander tous les valeurs et clefs pour la section spécifié dans un fichier «WIN.INI».
GetProfileSectionW | DWORD GetProfileSectionW( LPCWSTR lpAppName, LPWSTR lpReturnedString, DWORD nSize ); | kernel32.dll | Cette fonction permet de demander tous les valeurs et clefs pour la section spécifié dans un fichier «WIN.INI» avec des paramètres de chaine de caractères étendue (Unicode).
GetProfileStringA | DWORD GetProfileStringA( LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpDefault, LPTSTR lpReturnedString, DWORD nSize ); | kernel32.dll | Cette fonction permet de demander une chaine de caractères associé avec la clef spécifié dans une section du fichier «WIN.INI».
GetProfileStringW | DWORD GetProfileStringW( LPCWSTR lpAppName, LPCWSTR lpKeyName, LPCWSTR lpDefault, LPWSTR lpReturnedString, DWORD nSize ); | kernel32.dll | Cette fonction permet de demander une chaine de caractères associé avec la clef spécifié dans une section du fichier «WIN.INI» avec des paramètres de chaine de caractères étendue (Unicode).
GetPropA | HANDLE GetPropA(HWND hWnd, LPCTSTR lpString); | user32.dll | Cette fonction permet de demander une données Handle d'une liste de propriété d'une fenêtre.
GetPropW | HANDLE GetPropW(HWND hWnd, LPCWSTR lpString); | user32.dll | Cette fonction permet de demander une données Handle d'une liste de propriété d'une fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
GetQueuedCompletionStatus | BOOL GetQueuedCompletionStatus( HANDLE CompletionPort, LPDWORD lpNumberOfBytesTransferred, LPDWORD lpCompletionKey, LPOVERLAPPED * lpOverlapped, DWORD dwMilliseconds ); | kernel32.dll | Cette fonction permet d'essayer de compléter la file d'attente d'un paquet d'entrée/sortie spécifié pour un port d'entrée/sortie.
GetQueueStatus | DWORD GetQueueStatus(UINT flags); | user32.dll | Cette fonction permet de retourner une valeur indiquant le type de message dans la file d'attente.
GetRasterizerCaps | BOOL GetRasterizerCaps(LPRASTERIZER_STATUS lprs, UINT cb); | gdi32.dll | Cette fonction permet de retourner les drapeaux indiquant si la police de caractères TrueType est installé dans le système.
GetRegionData | DWORD GetRegionData(HRGN hRgn, DWORD dwCount, LPRGNDATA lpRgnData); | gdi32.dll | Cette fonction permet de remplir le tampons spécifié avec une région de données décrites.
GetRgnBox | int GetRgnBox(HRGN hrgn, LPRECT lprc); | gdi32.dll | Cette fonction permet de demander les coordonnées des limites rectangulaires d'une région spécifié.
GetROP2 | int GetROP2(HDC hdc); | gdi32.dll | Cette fonction permet de demander le mode dessinage courant.
GetScrollInfo | BOOL GetScrollInfo(HWND hwnd, int fnBar, LPSCROLLINFO lpsi); | user32.dll | Cette fonction permet de demander les paramètres d'une barre de défilement, en incluant les positions de défilement minimum et maximum, la taille de la page et la position de la boite de défilement.
GetScrollPos | int GetScrollPos(HWND hWnd, int nBar); | user32.dll | Cette fonction permet de demander la position courante de la boite de défilement d'une barre de défilement.
GetScrollRange | BOOL GetScrollRange(HWND hWnd, int nBar, LPINT lpMinPos, LPINT lpMaxPos); | user32.dll | Cette fonction permet de demander la position minimum et maximum de la barre de défilement spécifié.
GetSecurityDescriptorControl | BOOL GetSecurityDescriptorControl( PSECURITY_DESCRIPTOR pSecurityDescriptor, PSECURITY_DESCRIPTOR_CONTROL pControl, LPDWORD lpdwRevision ); | advapi32.dll | Cette fonction permet de demander le contrôle de descripteur de sécurité et les informations de révision.
GetSecurityDescriptorDacl | BOOL GetSecurityDescriptorDacl( PSECURITY_DESCRIPTOR pSecurityDescriptor, LPBOOL lpbDaclPresent, PACL *pDacl, LPBOOL lpbDaclDefaulted ); | advapi32.dll | Cette fonction permet de demander un pointeur sur une liste de contrôle d'accès (ACL) discrétionnaire dans un descripteur de sécurité spécifié.
GetSecurityDescriptorGroup | BOOL GetSecurityDescriptorGroup( PSECURITY_DESCRIPTOR pSecurityDescriptor, PSID * pGroup, LPBOOL lpbGroupDefaulted ); | advapi32.dll | Cette fonction permet de demander les informations de groupe primaire d'un descripteur de sécurité.
GetSecurityDescriptorLength | DWORD GetSecurityDescriptorLength( PSECURITY_DESCRIPTOR pSecurityDescriptor ); | advapi32.dll | Cette fonction permet de retourner la longueur, en octets, d'une structure valide de descripteur de sécurité.
GetSecurityDescriptorOwner | BOOL GetSecurityDescriptorOwner( PSECURITY_DESCRIPTOR pSecurityDescriptor, PSID * pOwner, LPBOOL lpbOwnerDefaulted ); | advapi32.dll | Cette fonction permet de demander les informations du propriétaire d'un descripteur de sécurité
GetSecurityDescriptorSacl | BOOL GetSecurityDescriptorSacl( PSECURITY_DESCRIPTOR pSecurityDescriptor, LPBOOL lpbSaclPresent, PACL *pSacl, LPBOOL lpbSaclDefaulted ); | advapi32.dll | Cette fonction permet de demander un pointeur sur la liste de contrôle d'accès système (ACL) dans un descripteur de sécurité spécifié.
GetShortPathNameA | DWORD GetShortPathNameA( LPCTSTR lpszLongPath, LPTSTR lpszShortPath, DWORD cchBuffer ); | kernel32.dll | Cette fonction permet de demander, à partir d'un nom long, le nom en format DOS (8 caractères de nom et 3 caractères d'extension) d'un fichier.
GetShortPathNameW | DWORD GetShortPathNameW( LPCWSTR lpszLongPath, LPWSTR lpszShortPath, DWORD cchBuffer ); | kernel32.dll | Cette fonction permet de demander, à partir d'un nom long, le nom en format DOS (8 caractères de nom et 3 caractères d'extension) d'un fichier avec des paramètres de chaine de caractères étendue (Unicode).
GetSidIdentifierAuthority | PSID_IDENTIFIER_AUTHORITY GetSidIdentifierAuthority( PSID pSid ); | advapi32.dll | Cette fonction permet de retourner l'adresse de la structure d'un «SID_IDENTIFIER_AUTHORITY» dans l'identificateur de sécurité (SID) spécifié.
GetSidLengthRequired | DWORD GetSidLengthRequired( UCHAR nSubAuthorityCount ); | advapi32.dll | Cette fonction permet de retourner la longueur, en octets, d'un tampon requis pour l'entreposage d'une structure SID avec le nombre de sous-autorisation spécifié.
GetSidSubAuthority | PDWORD GetSidSubAuthority( PSID pSid, DWORD nSubAuthority ); | advapi32.dll | Cette fonction permet de retourner l'adresse d'une sous-autorité spécifié d'une structure SID.
GetSidSubAuthorityCount | PUCHAR GetSidSubAuthorityCount( PSID pSid ); | advapi32.dll | Cette fonction permet de retourner l'adresse d'un champs d'une structure SID contenant le compteur de sous-autorité.
GetStartupInfoA | VOID GetStartupInfoA( LPSTARTUPINFO lpStartupInfo ); | kernel32.dll | Cette fonction permet de demander le contenu de la structure «STARTUPINFO» spécifié quand un appel de processus est créé.
GetStartupInfoW | VOID GetStartupInfoW( LPSTARTUPINFOW lpStartupInfo ); | kernel32.dll | Cette fonction permet de demander le contenu de la structure «STARTUPINFO» spécifié quand un appel de processus est créé avec des paramètres de chaine de caractères étendue (Unicode).
GetStdHandle | HANDLE GetStdHandle(DWORD nStdHandle); | kernel32.dll | Cette fonction permet de demander le Handle d'une entrée ou sortie standard.
GetStockObject | HGDIOBJ GetStockObject(int fnObject); | gdi32.dll | Cette fonction permet de demander le Handle d'un des outils de stylo, brosse, police de caractères,... des objets de Windows
GetStretchBltMode | int GetStretchBltMode(HDC hdc); | gdi32.dll | Cette fonction permet de demander le mode d'étirement du Bitmap courant.
GetStringTypeA | BOOL GetStringTypeA( LCID Locale, DWORD dwInfoType, LPCSTR lpSrcStr, int cchSrc, LPWORD lpCharType ); | kernel32.dll | Cette fonction permet de retourner des informations type de caractère pour la chaine de caractères spécifié.
GetStringTypeExA | BOOL GetStringTypeExA( LCID Locale, DWORD dwInfoType, LPCTSTR lpSrcStr, int cchSrc, LPWORD lpCharType ); | kernel32.dll | Cette fonction permet de retourner des informations type de caractère pour la chaine de caractères spécifié en Win32.
GetStringTypeExW | BOOL GetStringTypeExW( LCID locale, DWORD type, LPCWSTR src, INT count, LPWORD chartype ); | kernel32.dll | Cette fonction permet de retourner des informations type de caractère pour la chaine de caractères spécifié en Win32 avec des paramètres de chaine de caractères étendue (Unicode).
GetStringTypeW | BOOL GetStringTypeW( DWORD type, LPCWSTR src, INT count, LPWORD chartype ); | kernel32.dll | Cette fonction permet de retourner des informations type de caractère pour la chaine de caractères spécifié avec des paramètres de chaine de caractères étendue (Unicode).
GetSubMenu | HMENU GetSubMenu(HMENU hMenu, int nPos); | user32.dll | Cette fonction permet de demander le Handle du menu contextuel.
GetSysColor | DWORD GetSysColor(int nIndex); | user32.dll | Cette fonction permet de demander la couleur d'un des objets affichés du système d'exploitations de Windows.
GetSysColorBrush | HBRUSH GetSysColorBrush(int nIndex); | user32.dll | Cette fonction permet de demander le Handle identifiant la brosse logique correspondant à l'index de couleur spécifié.
GetSystemDefaultLCID | LCID GetSystemDefaultLCID(VOID); | kernel32.dll | Cette fonction permet de demander l'identificateur locale du système par défaut.
GetSystemDefaultLangID | LANGID GetSystemDefaultLangID(VOID); | kernel32.dll | Cette fonction permet de demander l'identificateur de langage système par défaut.
GetSystemDirectoryA | UINT GetSystemDirectoryA(LPTSTR lpBuffer,UINT uSize); | kernel32.dll | Cette fonction permet de demander le répertoire dans lequel les fichiers systèmes du système d'exploitations de Windows sont situés.
GetSystemDirectoryW | UINT GetSystemDirectoryW(LPWSTR lpBuffer, UINT uSize); | kernel32.dll | Cette fonction permet de demander le répertoire dans lequel les fichiers systèmes du système d'exploitations de Windows sont situés avec des paramètres de chaine de caractères étendue (Unicode).
GetSystemInfo | VOID GetSystemInfo(LPSYSTEM_INFO lpSystemInfo); | kernel32.dll | Cette fonction permet de demander divers informations à propos du système courant et du microprocesseur.
GetSystemMenu | HMENU GetSystemMenu(HWND hWnd,BOOL bRevert); | user32.dll | Cette fonction permet de demander le Handle du menu système d'une fenêtre d'application.
GetSystemMetrics | int GetSystemMetrics(int nIndex); | user32.dll | Cette fonction permet de demander le type de système métrique ou de ses paramètres utilisés.
GetSystemPaletteEntries | UINT GetSystemPaletteEntries(HDC hdc, UINT iStartIndex, UINT nEntries, LPPALETTEENTRY lppe); | gdi32.dll | Cette fonction permet de demander un rang dans les entrées de palette de la palette système.
GetSystemPaletteUse | UINT GetSystemPaletteUse(HDC hdc); | gdi32.dll | Cette fonction permet de demander si une application est accessible à une palette d'entrée système.
GetSystemPowerStatus | BOOL GetSystemPowerStatus(LPSYSTEM_POWER_STATUS lpSystemPowerStatus); | kernel32.dll | Cette fonction permet de demander l'état de l'alimentation en énergie de l'ordinateur.
GetSystemTime | VOID GetSystemTime(LPSYSTEMTIME lpSystemTime); | kernel32.dll | Cette fonction permet de demander le temps (date et heure) courant du système d'exploitation. Le temps du système d'exploitation est une expression UTC (Coordinated Universal Time).
GetSystemTimeAsFileTime | VOID GetSystemTimeAsFileTime(LPFILETIME lpSystemTimeAsFileTime); | kernel32.dll | Cette fonction permet de demander la date et l'heure du système d'exploitation.
GetSystemTimeAdjustment | BOOL GetSystemTimeAdjustment( PDWORD lpTimeAdjustment, PDWORD lpTimeIncrement, PBOOL lpTimeAdjustmentDisabled ); | kernel32.dll | Cette fonction permet de demander si le système applique périodiquement un ajustement du temps après chaque tic d'horloge.
GetTabbedTextExtentA | DWORD GetTabbedTextExtentA(HDC hDC, LPCTSTR lpString, int nCount, int nTabPositions, LPINT lpnTabStopPositions); | user32.dll | Cette fonction permet de calculer la largeur et la hauteur d'une chaine de caractères.
GetTabbedTextExtentW | DWORD GetTabbedTextExtentW(HDC hDC, LPCWSTR lpString, int nCount, int nTabPositions, LPINT lpnTabStopPositions); | user32.dll | Cette fonction permet de calculer la largeur et la hauteur d'une chaine de caractères avec des paramètres de chaine de caractères étendues (Unicode).
GetTapeParameters | DWORD GetTapeParameters( HANDLE hDevice, DWORD dwOperation, LPDWORD lpdwSize, LPVOID lpTapeInformation ); | kernel32.dll | Cette fonction permet de demander des informations descriptives sur l'unité de bande magnétique de copie de sauvegarde.
GetTapePosition | DWORD GetTapePosition( HANDLE hDevice, DWORD dwPositionType, LPDWORD lpdwPartition, LPDWORD lpdwOffsetLow, LPDWORD lpdwOffsetHigh ); | kernel32.dll | Cette fonction permet de demander l'adresse courante logique ou en bloc absolue sur une bande magnétique de copie de sauvegarde.
GetTapeStatus | DWORD GetTapeStatus(HANDLE hDevice); | kernel32.dll | Cette fonction permet de demander si la bande magnétique de copie de sauvegarde est prête à recevoir une commande de processus.
GetTcpStatistics | DWORD GetTcpStatistics(PMIB_TCPSTATS pStats); | iphlpapi.dll | Cette fonction permet de demander les informations de statistique sur le TCP de l'ordinateur.
GetTempFileNameA | UINT GetTempFileNameA(LPCTSTR lpPathName, LPCTSTR lpPrefixString, UINT uUnique, LPTSTR lpTempFileName); | kernel32.dll | Cette fonction permet de demander un nom de fichier créer temporairement.
GetTempFileNameW | UINT GetTempFileNameW( LPCWSTR lpPathName, LPCWSTR lpPrefixString, UINT unique, LPWSTR lpTempFileName ); | kernel32.dll | Cette fonction permet de demander un nom de fichier créer temporairement avec des paramètres de chaine de caractères étendue (Unicode).
GetTempPathA | DWORD GetTempPathA(DWORD nBufferLength,LPTSTR lpBuffer); | kernel32.dll | Cette fonction permet de demander le répertoire par défaut des fichiers temporairement.
GetTempPathW | DWORD GetTempPathW(DWORD nBufferLength,LPWSTR lpBuffer); | kernel32.dll | Cette fonction permet de demander le répertoire par défaut des fichiers temporairement avec des paramètres de chaine de caractères étendue (Unicode).
GetTextAlign | UINT GetTextAlign(HDC hdc); | gdi32.dll | Cette fonction permet de demander l'état des drapeaux d'alignement de texte pour un périphérique de contexte spécifié.
GetTextCharacterExtra | int GetTextCharacterExtra(HDC hdc); | gdi32.dll | Cette fonction permet de demander l'ajustement courant pour la quantité d'espacement entre les caractères.
GetTextCharset | UINT GetTextCharsetInfo(HDC hdc, LPFONTSIGNATURE * lpSig, DWORD dwFlags); | gdi32.dll | Cette fonction permet de demander les informations à propos d'un ensemble de caractères de la police de caractères actuellement sélectionné.
GetTextCharsetInfo | UINT GetTextCharsetInfo(HDC hdc, LPFONTSIGNATURE * lpSig, DWORD dwFlags); | gdi32.dll | Cette fonction permet de demander les informations supplémentaires à propos de l'ensemble de caractères de la police de caractères actuellement sélectionné.
GetTextColor | COLORREF GetTextColor(HDC hdc); | gdi32.dll | Cette fonction permet de demander la couleur de texte courante.
GetTextExtentExPointA | BOOL GetTextExtentExPointA(HDC hdc, LPCTSTR lpszStr, int cchString, int nMaxExtent, LPINT lpnFit, LPINT alpDx, LPSIZE lpSize); | gdi32.dll | Cette fonction permet de demander le nombre de caractères dans la chaine de caractères correspondant à l'espace spécifié et remplit un tableau avec le texte étendue pour chacun de ses caractères.
GetTextExtentExPointW | BOOL GetTextExtentExPointW(HDC hdc, LPCWSTR lpszStr, int cchString, int nMaxExtent, LPINT lpnFit, LPINT alpDx, LPSIZE lpSize); | gdi32.dll | Cette fonction permet de demander le nombre de caractères dans la chaine de caractères correspondant à l'espace spécifié et remplit un tableau avec le texte étendue pour chacun de ses caractères avec des paramètres de chaine de caractères étendues (Unicode).
GetTextExtentPoint32A | BOOL GetTextExtentPoint32A(HDC hdc, LPCTSTR lpString, int cbString, LPSIZE lpSize); | gdi32.dll | Cette fonction permet de calculer la largeur et la hauteur de la chaine de caractères de texte spécifié.
GetTextExtentPoint32W | BOOL GetTextExtentPoint32W(HDC hdc, LPCWSTR str, INT count, LPSIZE size); | gdi32.dll | Cette fonction permet de calculer la largeur et la hauteur de la chaine de caractères de texte spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetTextExtentPointA | BOOL GetTextExtentPointA(HDC hdc, LPCTSTR lpString, int cbString, LPSIZE lpSize); | gdi32.dll | Cette fonction permet de calculer la largeur et la hauteur d'un chaine de caractères de texte spécifié dans un contexte de compatibilité 16 bits.
GetTextExtentPointW | BOOL GetTextExtentPointW(HDC hdc, LPCWSTR lpString, int cbString, LPSIZE lpSize); | gdi32.dll | Cette fonction permet de calculer la largeur et la hauteur d'un chaine de caractères de texte spécifié dans un contexte de compatibilité 16 bits avec des paramètres de chaine de caractères étendues (Unicode).
GetTextFaceA | int GetTextFaceA(HDC hdc, int nCount, LPTSTR lpFaceName); | gdi32.dll | Cette fonction permet de demander le nom de type de police de caractères étant sélectionné dans le périphérique de contexte spécifié.
GetTextFaceW | int GetTextFaceW(HDC hdc, int nCount, LPWSTR lpFaceName); | gdi32.dll | Cette fonction permet de demander le nom de type de police de caractères étant sélectionné dans le périphérique de contexte spécifié avec des paramètres de chaine de caractères étendues (Unicode).
GetTextMetricsA | BOOL GetTextMetricsA(HDC hdc, LPTEXTMETRIC lptm); | gdi32.dll | Cette fonction permet de demander les informations métriques d'une police de caractères courante.
GetTextMetricsW | BOOL GetTextMetricsW(HDC hdc, LPTEXTMETRICW lptm); | gdi32.dll | Cette fonction permet de demander les informations métriques d'une police de caractères courante avec des paramètres de chaine de caractères étendues (Unicode).
GetThreadContext | BOOL GetThreadContext( HANDLE hThread, LPCONTEXT lpContext ); | kernel32.dll | Cette fonction permet de demander le contexte d'un processus léger spécifié.
GetThreadDesktop | HDESK GetThreadDesktop(DWORD dwThreadId); | user32.dll | Cette fonction permet de demander le Handle du bureau associé avec le processus spécifié.
GetThreadLocale | LCID GetThreadLocale(VOID); | kernel32.dll | Cette fonction permet de demander l'identificateur local d'un processus.
GetThreadPriority | int GetThreadPriority(HANDLE hThread); | kernel32.dll | Cette fonction permet de retourner la valeur de priorité du processus léger spécifié.
GetThreadPriorityBoost | BOOL GetThreadPriorityBoost(HANDLE hthread, PBOOL pstate); | kernel32.dll | Cette fonction permet de retourner la priorité de lancement d'un processus léger.
GetThreadSelectorEntry | BOOL GetThreadSelectorEntry( HANDLE hThread, DWORD dwSelector, LPLDT_ENTRY lpSelectorEntry ); | kernel32.dll | Cette fonction permet de retourner l'entrée de descripteur de table pour le sélecteur et le processus léger spécifié.
GetThreadTimes | BOOL GetThreadTimes( HANDLE hThread, LPFILETIME lpCreationTime, LPFILETIME lpExitTime, LPFILETIME lpKernelTime, LPFILETIME lpUserTime ); | kernel32.dll | Cette fonction permet de demander les informations de temps à propos du processus léger spécifié.
GetTickCount | DWORD GetTickCount(VOID); | kernel32.dll | Cette fonction permet de demander le nombre de milliseconde écoulé depuis le démarrage de Windows.
GetTimeFormatA | int GetTimeFormatA( LCID Locale, DWORD dwFlags, CONST SYSTEMTIME * lpTime, LPCTSTR lpFormat, LPTSTR lpTimeStr, int cchTime ); | kernel32.dll | Cette fonction permet de retourner la chaine de caractères du format du temps localement.
GetTimeFormatW | int GetTimeFormatW( LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime, LPCWSTR lpFormat, LPWSTR lpTimeStr, int cchOut ); | kernel32.dll | Cette fonction permet de retourner la chaine de caractères du format du temps localement avec des paramètres de chaine de caractères étendue (Unicode).
GetTimeZoneInformation | DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION lpTimeZoneInformation); | kernel32.dll | Cette fonction permet de demander les informations de la zone horaire courante du système d'exploitation Windows.
GetTokenInformation | BOOL GetTokenInformation( HANDLE TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, LPVOID TokenInformation, DWORD TokenInformationLength, PDWORD ReturnLength ); | advapi32.dll | Cette fonction permet de demander le type d'information associé à un accès de jeton.
GetTopWindow | HWND GetTopWindow(HWND hWnd); | user32.dll | Cette fonction permet de demander le Handle du plus haut niveau de fenêtre enfant appartenant à la fenêtre parent spécifié.
GetUdpStatistics | DWORD GetUdpStatistics(PMIB_UDPSTATS pStats); | iphlpapi.dll | Cette fonction permet de demander les informations de statistique sur le UDP de l'ordinateur.
GetUpdateRect | BOOL GetUpdateRect(HWND hWnd, LPRECT lpRect, BOOL bErase); | user32.dll | Cette fonction permet de demander les coordonnées d'un petit rectangle complètement enfermé dans une région mise à jour par la fenêtre donnée.
GetUpdateRgn | int GetUpdateRgn(HWND hWnd, HRGN hRgn, BOOL bErase); | user32.dll | Cette fonction permet de demander la région mise à jour dans la fenêtre.
GetUserDefaultLCID | LCID GetUserDefaultLCID(VOID); | kernel32.dll | Cette fonction permet de demander l'identificateur local de l'utilisateur par défaut.
GetUserNameA | BOOL GetUserNameA(LPTSTR lpBuffer, LPDWORD nSize); | advapi32.dll | Cette fonction permet de demander le nom de l'utilisateur courant actuellement sous Windows.
GetUserNameW | BOOL GetUserNameW( LPWSTR lpszName, LPDWORD lpSize ); | advapi32.dll | Cette fonction permet de demander le nom de l'utilisateur courant actuellement sous le système d'exploitation Windows avec des paramètres de chaine de caractères étendue (Unicode).
GetUserNameExA | BOOLEAN GetUserNameExA( EXTENDED_NAME_FORMAT NameFormat, LPTSTR lpNameBuffer, PULONG nSize ); | secur32.dll | Cette fonction permet de retourner le nom de l'utilisateur dans le format EXTENDED_NAME_FORMAT.
GetUserObjectInformationA | BOOL GetUserObjectInformationA(HANDLE hObj, int nIndex, PVOID pvInfo, DWORD nLength,LPDWORD lpnLengthNeeded); | user32.dll | Cette fonction permet de demander les informations à propos d'une station de fenêtre ou d'un objet de bureau.
GetUserObjectInformationW | BOOL GetUserObjectInformationW(HANDLE hObj, int nIndex, PVOID pvInfo, DWORD nLength,LPDWORD lpnLengthNeeded); | user32.dll | Cette fonction permet de demander les informations à propos d'une station de fenêtre ou d'un objet de bureau avec des paramètres de chaine de caractères étendues (Unicode).
GetUserObjectSecurity | BOOL GetUserObjectSecurity(HANDLE hObj, PSECURITY_INFORMATION pSIRequested, PSECURITY_DESCRIPTOR pSID, DWORD nLength, LPDWORD lpnLengthNeeded); | user32.dll | Cette fonction permet de demander les informations de sécurité pour l'objet utilisateur spécifié.
GetVersion | DWORD GetVersion(VOID); | kernel32.dll | Cette fonction permet de demander le numéro de version courante du système d'exploitation Windows et les informations à propos de la plateforme du système d'exploitation.
GetVersionExA | BOOL GetVersionExA(LPOSVERSIONINFO lpVersionInformation); | kernel32.dll | Cette fonction permet de demander la version du système d'exploitation Windows.
GetVersionExW | BOOL GetVersionExW(LPOSVERSIONINFOW lpVersionInformation); | kernel32.dll | Cette fonction permet de demander la version du système d'exploitation Windows avec des paramètres de chaine de caractères étendue (Unicode).
GetViewportExtEx | BOOL GetViewportExtEx(HDC hdc, LPSIZE lpSize); | gdi32.dll | Cette fonction permet de demander les extensions X et Y d'un point de vue d'un périphérique de contexte.
GetViewportOrgEx | BOOL GetViewportOrgEx(HDC hdc, LPPOINT lpPoint); | gdi32.dll | Cette fonction permet de demander les coordonnées X et Y d'origine d'une association de point vue avec un périphérique de contexte spécifié.
GetVolumeInformationA | BOOL GetVolumeInformationA( LPCTSTR lpRootPathName, LPTSTR lpVolumeNameBuffer, DWORD nVolumeNameSize, LPDWORD lpVolumeSerialNumber, LPDWORD lpMaximumComponentLength, LPDWORD lpFileSystemFlags, LPTSTR lpFileSystemNameBuffer, DWORD nFileSystemNameSize ); | kernel32.dll | Cette fonction permet de demander les informations d'entête d'une unité disque.
GetVolumeInformationW | BOOL GetVolumeInformationW( LPCWSTR lpRootPathName, LPWSTR lpVolumeNameBuffer, DWORD nVolumeNameSize, PDWORD lpVolumeSerialNumber, PDWORD lpMaximumComponentLength, PDWORD lpFileSystemFlags, LPWSTR lpFileSystemNameBuffer, DWORD nFileSystemNameSize ); | kernel32.dll | Cette fonction permet de demander les informations d'entête d'une unité disque avec des paramètres de chaine de caractères étendue (Unicode).
GetWindow | HWND GetWindow(HWND hWnd,UINT uCmd); | user32.dll | Cette fonction permet de demander le Handle d'une fenêtre d'application.
GetWindowContextHelpId | DWORD GetWindowContextHelpId(HWND hwnd); | user32.dll | Cette fonction permet de demander l'identificateur de contexte, si possible, associé avec la fenêtre spécifié.
GetWindowDC | HDC GetWindowDC(HWND hWnd); | user32.dll | Cette fonction permet de demander un périphérique de contexte pour la fenêtre, la barre de titre, les menus et la barre de défilement.
GetWindowLongA | LONG GetWindowLongA(HWND hWnd,int nIndex); | user32.dll | Cette fonction permet de demander, sous forme d'une valeur de 32 bits, une des propriétés d'une fenêtre d'application.
GetWindowLongW | LONG GetWindowLongW(HWND hWnd, int nIndex); | user32.dll | Cette fonction permet de demander, sous forme d'une valeur de 32 bits, une des propriétés d'une fenêtre d'application avec des paramètres de chaine de caractères étendues (Unicode).
GetWindowPlacement | BOOL GetWindowPlacement(HWND hWnd, WINDOWPLACEMENT * lpwndpl); | user32.dll | Cette fonction permet de demander l'état d'affichage et de restituer, minimiser et maximiser les positions d'une fenêtre.
GetWindowRect | BOOL GetWindowRect(HWND hWnd,LPRECT lpRect); | user32.dll | Cette fonction permet de demander les coordonnées et la taille d'une fenêtre d'application.
GetWindowRgn | int GetWindowRgn(HWND hWnd, HRGN hRgn); | user32.dll | Cette fonction permet de demander une copie d'une région de fenêtre dans une fenêtre.
GetWindowsDirectoryA | UINT GetWindowsDirectoryA(LPTSTR lpBuffer,UINT uSize); | kernel32.dll | Cette fonction permet de demander le répertoire dans lequel le système d'exploitation Windows est situés.
GetWindowsDirectoryW | UINT GetWindowsDirectoryW(LPWSTR lpBuffer,UINT uSize); | kernel32.dll | Cette fonction permet de demander le répertoire dans lequel le système d'exploitation Windows est situés avec des paramètres de chaine de caractères étendue (Unicode).
GetWindowTextA | int GetWindowTextA(HWND hWnd, LPTSTR lpString, int nMaxCount); | user32.dll | Cette fonction permet de demander du texte contenu dans une fenêtre d'application.
GetWindowTextLengthA | int GetWindowTextLengthA(HWND hWnd); | user32.dll | Cette fonction permet de demander la longueur du texte contenu dans une fenêtre d'application.
GetWindowTextLengthW | int GetWindowTextLengthW(HWND hWnd); | user32.dll | Cette fonction permet de demander la longueur du texte contenu dans une fenêtre d'application avec des paramètres de chaine de caractères étendues (Unicode).
GetWindowTextW | int GetWindowTextW(HWND hWnd, LPWSTR lpString, int nMaxCount); | user32.dll | Cette fonction permet de demander du texte contenu dans une fenêtre d'application avec des paramètres de chaine de caractères étendues (Unicode).
GetWindowThreadProcessId | DWORD GetWindowThreadProcessId(HWND hWnd, LPDWORD lpdwProcessId); | user32.dll | Cette fonction permet de demander l'identificateur de processus léger étant créer par la fenêtre spécifié et, optionnellement, l'identificateur du processus créant la fenêtre.
GetWindowWord | WORD GetWindowWord(HWND hWnd, int nIndex); | user32.dll | Cette fonction permet de demander une valeur 16 bits de déplacement spécifié dans une fenêtre en mémoire supplémentaire pour la fenêtre spécifié.
GetWinMetaFileBits | UINT GetWinMetaFileBits(HENHMETAFILE hemf, UINT cbBuffer, LPBYTE lpbBuffer, INT fnMapMode, HDC hdcRef); | gdi32.dll | Cette fonction permet de convertir des enregistrements de format étendue en enregistrement de format Windows de fichier méta et d'entreposer les enregistrements convertie dans le tampon spécifié.
GetWindowExtEx | BOOL GetWindowExtEx(HDC hdc, LPSIZE lpSize); | gdi32.dll | Cette fonction permet de demander les extensions X et Y d'une fenêtre associé avec un périphérique de contexte spécifié.
GetWindowOrgEx | BOOL GetWindowOrgEx(HDC hdc, LPPOINT lpPoint); | gdi32.dll | Cette fonction permet de demander les coordonnées X et Y d'origine de la fenêtre associé avec le périphérique de contexte spécifié.
GetWorldTransform | BOOL GetWorldTransform(HDC hdc, LPXFORM lpXform); | gdi32.dll | Cette fonction permet de demander l'espace mondiale courant vers la transformation d'espace de page.
glAccum | void glAccum(GLenum op,GLfloat value); | opengl32.dll | Cette fonction permet d'effecter un contrôle du tampon d'accumulation sous OpenGL.
glAlphaFunc | void glAlphaFunc(GLenum func,GLclampf ref); | opengl32.dll | Cette fonction permet de définir la fonction devant servir de test Alpha sous OpenGL.
glBegin | void glBegin(GLenum mode); | opengl32.dll | Cette fonction permet de définir le commencement de la limite d'une primitive ou d'un groupe de primitive sous OpenGL.
glBitmap | void glBitmap(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); | opengl32.dll | Cette fonction permet le contenu d'une image Bitmap sous OpenGL.
glBlendFunc | void glBlendFunc(GLenum sfactor, GLenum dfactor); | opengl32.dll | Cette fonction permet de définir les facteurs de blending sous OpenGL.
glCallList | void glCallList(GLuint list); | opengl32.dll | Cette fonction permet d'appeler une liste de commande graphique OpenGL.
glCallLists | void glCallLists(GLsizei n, GLenum type, const GLvoid *lists); | opengl32.dll | Cette fonction permet d'appeler plusieurs listes de commande graphique OpenGL.
glClear | void glClear(GLbitfield mask); | opengl32.dll | Cette fonction permet de vider les tampons du OpenGL ainsi qu'effacement du contenu de la fenêtre.
glClearAccum | void glClearAccum(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); | opengl32.dll | Cette fonction permet de vider le tampon d'accumulation du OpenGL.
glClearColor | void glClearColor(GLclampf red, GLclampf green, GLclampf blue,GLclampf alpha); | opengl32.dll | Cette fonction permet de vider le tampon chromatique du OpenGL.
glClearDepth | void glClearDepth(GLclampd depth); | opengl32.dll | Cette fonction permet de vider le tampon de profondeur du OpenGL.
glClearIndex | void glClearIndex(GLfloat c); | opengl32.dll | Cette fonction permet de vider le mode index du OpenGL.
glClearStencil | void glClearStencil(GLint s); | opengl32.dll | Cette fonction permet de vider le tampon stencil du OpenGL.
glClipPlane | void glClipPlane(GLenum plane, const GLdouble *equation); | opengl32.dll | Cette fonction permet de définir un nouveau plan de clipping du OpenGL.
glColor3b | void glColor3b(GLbyte red,GLbyte green,GLbyte blue); | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'octet dans OpenGL.
glColor3bv | void glColor3bv(const GLbyte *v); | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'une structure d'octets dans OpenGL.
glColor3d | void glColor3d(GLdouble red, GLdouble green, GLdouble blue); | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'un réel double dans OpenGL.
glColor3dv | void glColor3dv(const GLdouble *v); | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'une structure des réels doubles dans OpenGL.
glColor3f | void glColor3f(GLfloat red, GLfloat green, GLfloat blue); | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'un réel dans OpenGL.
glColor3fv | void glColor3fv(const GLfloat *v); | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'une structure des réels dans OpenGL.
glColor3i | void glColor3i(GLint red, GLint green, GLint blue); | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'un entier long dans OpenGL.
glColor3iv | void glColor3iv(const GLint *v); | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'une structure des entiers long dans OpenGL.
glColor3s | void glColor3s(GLshort red, GLshort green, GLshort blue, GLshort alpha); | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'un entier court dans OpenGL.
glColor3sv | void glColor3sv(const GLshort *v); | opengl32.dll | Cette fonction permet de définir la couleur RVB (RGB) sous format d'une structure des entiers court dans OpenGL.
glEnd | void glEnd(void); | opengl32.dll | Cette fonction permet de définir la fin de la limite d'une primitive ou d'un groupe de primitive sous OpenGL.
GlobalAddAtomA | ATOM GlobalAddAtomA(LPCTSTR lpString); | kernel32.dll | Cette fonction permet d'ajouter un caractère à une table global d'Atom et retourne une valeur unique d'identificateur par chaine de caractères.
GlobalAddAtomW | ATOM GlobalAddAtomW(LPCWSTR str); | kernel32.dll | Cette fonction permet d'ajouter un caractère à une table global d'Atom et retourne une valeur unique d'identificateur par chaine de caractères avec des paramètres de chaine de caractères étendue (Unicode).
GlobalAlloc | HGLOBAL GlobalAlloc(UINT uFlags, DWORD dwBytes); | kernel32.dll | Cette fonction permet d'allouer de la mémoire dans les ressources global du système d'exploitation.
GlobalCompact | SIZE_T GlobalCompact(DWORD MinFree); | kernel32.dll | Cette fonction permet de réorganiser les allocations de mémoire dynamique du tas global en spécifiant une taille de mémoire minimum.
GlobalDeleteAtom | ATOM GlobalDeleteAtom(ATOM nAtom); | kernel32.dll | Cette fonction permet de décrémenté le compteur de référence de la chaine de caractères d'Atom global.
GlobalFindAtomA | ATOM GlobalFindAtomA(LPCTSTR lpString); | kernel32.dll | Cette fonction permet de rechercher une table d'Atom global dans la chaine de caractères spécifié et demande l'Atom global associé avec la chaine de caractères.
GlobalFindAtomW | ATOM GlobalFindAtomW(LPCWSTR str); | kernel32.dll | Cette fonction permet de rechercher une table d'Atom global dans la chaine de caractères spécifié et demande l'Atom global associé avec la chaine de caractères étendue (Unicode).
GlobalFix | void GlobalFix(HGLOBAL hglb); | kernel32.dll | Cette fonction permet de prévenir l'objet de mémoire global donnée contre un mouvement de mémoire linéaire. Cette fonction est obsolète.
GlobalFlags | UINT GlobalFlags(HGLOBAL hMem); | kernel32.dll | Cette fonction permet de retourner les informations à propos de l'objet de mémoire global spécifié.
GlobalFree | HGLOBAL GlobalFree(HGLOBAL hMem); | kernel32.dll | Cette fonction permet de libérer de la mémoire dans les ressources global du système d'exploitation.
GlobalGetAtomNameA | UINT GlobalGetAtomNameA(ATOM nAtom, LPTSTR lpBuffer, int nSize); | kernel32.dll | Cette fonction permet de demander une copie de la chaine de caractères associé avec l'Atom global donnée.
GlobalGetAtomNameW | UINT GlobalGetAtomNameW(ATOM atom, LPWSTR buffer, INT count); | kernel32.dll | Cette fonction permet de demander une copie de la chaine de caractères associé avec l'Atom global donnée avec des paramètres de chaine de caractères étendue (Unicode).
GlobalHandle | HGLOBAL GlobalHandle(LPCVOID pMem); | kernel32.dll | Cette fonction permet de demander le Handle de l'objet de mémoire global spécifié.
GlobalLock | LPVOID GlobalLock(HGLOBAL hMem); | kernel32.dll | Cette fonction permet de verrouiller l'emplacement du bloc de mémoire dans les ressources global du système d'exploitation.
GlobalMemoryStatus | VOID GlobalMemoryStatus(LPMEMORYSTATUS lpBuffer); | kernel32.dll | Cette fonction permet de demander l'état de la mémoire du système d'exploitation.
GlobalMemoryStatusEx | BOOL GlobalMemoryStatusEx(LPMEMORYSTATUSEX lpBuffer); | kernel32.dll | Cette fonction permet de demander l'état de la mémoire du système d'exploitation ayant plus de 4 Go de RAM d'installer sur l'ordinateur.
GlobalReAlloc | HGLOBAL GlobalReAlloc(HGLOBAL hMem, DWORD dwBytes, UINT uFlags); | kernel32.dll | Cette fonction permet de changer la taille ou les attributs de l'objet de mémoire global spécifié.
GlobalSize | DWORD GlobalSize(HGLOBAL hMem); | kernel32.dll | Cette fonction permet de demander la taille courante, en octets, de l'objet de mémoire global.
GlobalUnfix | VOID GlobalUnfix(HGLOBAL hMem); | kernel32.dll | Cette fonction permet d'annuler les effets d'une fonction GlobalFix et d'allouer l'objet de mémoire global a être déplacé dans la mémoire linéaire. Cette fonction est obsolète.
GlobalUnlock | BOOL GlobalUnlock(HGLOBAL hMem); | kernel32.dll | Cette fonction permet de déverrouiller l'emplacement du bloc de mémoire dans les ressources global du système d'exploitation.
GlobalUnWire | BOOL GlobalUnWire(HGLOBAL hMem); | kernel32.dll | Cette fonction permet de restituer la convertion d'un pointeur de processus 32 bits après une allocation de mémoire.
GrayStringA | BOOL GrayStringA(HDC hDC, HBRUSH hBrush, GRAYSTRINGPROC lpOutputFunc, LPARAM lpData, int nCount, int X, int Y, int nWidth, int nHeight); | user32.dll | Cette fonction permet de dessiner du texte grisé à l'emplacement donnée par l'écriture du texte dans un Bitmap en mémoire, grisonnant le Bitmap, et copie alors le Bitmap dans l'affichage.
GrayStringW | BOOL GrayStringW(HDC hDC, HBRUSH hBrush, GRAYSTRINGPROC lpOutputFunc, LPARAM lpData, int nCount, int X, int Y, int nWidth, int nHeight); | user32.dll | Cette fonction permet de dessiner du texte grisé à l'emplacement donnée par l'écriture du texte dans un Bitmap en mémoire, grisonnant le Bitmap, et copie alors le Bitmap dans l'affichage avec des paramètres de chaine de caractères étendues (Unicode).
HeapAlloc | LPVOID HeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes); | kernel32.dll | Cette fonction permet d'allouer un bloc de mémoire à l'aide d'un tas.
HeapCompact | SIZE_T HeapCompact(HANDLE hHeap, DWORD dwFlags); | kernel32.dll | Cette fonction permet de demander la taille de la largeur d'un bloc de mémoire disponible dans le tas spécifié.
HeapCreate | HANDLE HeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize); | kernel32.dll | Cette fonction permet de créer un objet de tas privé pouvant être utilisé par le processus appelant.
HeapDestroy | BOOL HeapDestroy(HANDLE hHeap); | kernel32.dll | Cette fonction permet de détruire l'objet de tas spécifié.
HeapFree | BOOL HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem); | kernel32.dll | Cette fonction permet de libérer un bloc de mémoire précédemment alloué d'un tas par la fonction HeapAlloc ou HeapReAlloc.
HeapLock | BOOL HeapLock(HANDLE hHeap); | kernel32.dll | Cette fonction permet de verrouiller l'association avec le tas spécifié dans un objet de section critique.
HeapReAlloc | LPVOID HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBytes); | kernel32.dll | Cette fonction permet de réallouer un bloc de mémoire d'un tas.
HeapSize | SIZE_T HeapSize(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem); | kernel32.dll | Cette fonction permet de demander la taille d'un bloc de mémoire alloué dans le tas par la fonction HeapAlloc ou HeapReAlloc.
HeapUnlock | BOOL HeapUnlock(HANDLE hHeap); | kernel32.dll | Cette fonction permet de déverrouiller l'association avec le tas spécifié dans un objet de section critique.
HeapValidate | BOOL HeapValidate(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem); | kernel32.dll | Cette fonction permet de valider le tas spécifié.
HeapWalk | BOOL HeapWalk(HANDLE hHeap, LPPROCESS_HEAP_ENTRY lpEntry); | kernel32.dll | Cette fonction permet d'énumérer les blocs de mémoire dans le tas spécifié.
HideCaret | BOOL HideCaret(HWND hWnd); | user32.dll | Cette fonction permet de cacher un caret en l'enlevant de l'écran.
HiliteMenuItem | BOOL HiliteMenuItem(HWND hwnd, HMENU hmenu, UINT uItemHilite, UINT uHilite); | user32.dll | Cette fonction permet de faire passer un item de menu en mode de surbrillance.
IcmpCloseHandle | BOOL IcmpCloseHandle(HANDLE IcmpHandle); | icmp.dll | Cette fonction permet de fermer un fichier sur le protocole ICMP (Internet Control Message Protocol).
IcmpCreateFile | HANDLE IcmpCreateFile(void); | icmp.dll | Cette fonction permet de créer un fichier sur le protocole ICMP (Internet Control Message Protocol).
IcmpSendEcho | DWORD IcmpSendEcho( HANDLE IcmpHandle, IPAddr DestinationAddress, LPVOID RequestData, WORD RequestSize, PIP_OPTION_INFORMATION RequestOptions, LPVOID ReplyBuffer, DWORD ReplySize, DWORD Timeout ); | icmp.dll | Cette fonction permet d'envoyer une requête en écho par le protocole ICMP (Internet Control Message Protocol).
ImpersonateDdeClientWindow | BOOL ImpersonateDdeClientWindow(HWND hWndClient, HWND hWndServer); | user32.dll | Cette fonction permet d'activer l'échange de données dynamique (DDE) vers un contexte d'application sécurisé d'imitation de client DDE.
ImpersonateLoggedOnUser | BOOL ImpersonateLoggedOnUser( HANDLE hToken ); | advapi32.dll | Cette fonction permet d'appeler une processus léger utilisateur impersonnel.
ImpersonateNamedPipeClient | BOOL ImpersonateNamedPipeClient( HANDLE hNamedPipe ); | advapi32.dll | Cette fonction permet de demmander si l'application client a un nom de canal impersonnel.
ImpersonateSelf | BOOL ImpersonateSelf( SECURITY_IMPERSONATION_LEVEL ImpersonationLevel ); | advapi32.dll | Cette fonction permet de demander le jeton d'accès au contexte de sécurité impersonnel d'un processus appelant.
inet_addr | unsigned long inet_addr (const char FAR * cp); | Wsock32.dll | Cette fonction permet l'adresse IP contenu dans une chaine de caractères sous sa forme numérique 32 bits.
InflateRect | BOOL InflateRect(LPRECT lprc, int dx, int dy); | user32.dll | Cette fonction permet d'agrandir ou diminuer la taille d'un objet de rectangle.
InitAtomTable | BOOL InitAtomTable(DWORD nSize); | kernel32.dll | Cette fonction permet d'initialiser la table d'Atom local et fixe la taille spécifié.
InitCommonControlsEx | BOOL InitCommonControlsEx(LPINITCOMMONCONTROLSEX pInitCtrls); | comctl32.dll | Cette fonction permet d'initialiser une classe de contrôle commun.
InitializeAcl | BOOL InitializeAcl( PACL pAcl, DWORD nAclLength, DWORD dwAclRevision ); | advapi32.dll | Cette fonction permet de créer une nouvelle structure de liste de contrôle d'accès (ACL).
InitializeCriticalSection | VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); | kernel32.dll | Cette fonction permet d'initialiser un objet de section critique.
InitializeSecurityDescriptor | BOOL InitializeSecurityDescriptor( PSECURITY_DESCRIPTOR pSecurityDescriptor, DWORD dwRevision ); | advapi32.dll | Cette fonction permet d'initialiser un nouveau descripteur de sécurité.
InitializeSid | BOOL InitializeSid( PSID Sid, PSID_IDENTIFIER_AUTHORITY pIdentifierAuthority, BYTE nSubAuthorityCount ); | advapi32.dll | Cette fonction permet d'initialiser une structure d'identificateur de sécurité (SID).
InitiateSystemShutdownA | BOOL InitiateSystemShutdownA( LPSTR lpMachineName, LPSTR lpMessage, DWORD dwTimeout, BOOL bForceAppsClosed, BOOL bRebootAfterShutdown ); | advapi32.dll | Cette fonction permet d'initier l'opération de fermeture ou de redémarrage en cours d'exécution du système d'exploitation Windows.
InitiateSystemShutdownW | BOOL InitiateSystemShutdownW( LPWSTR lpMachineName, LPWSTR lpMessage, DWORD dwTimeout, BOOL bForceAppsClosed, BOOL bRebootAfterShutdown ); | advapi32.dll | Cette fonction permet d'initier l'opération de fermeture ou de redémarrage en cours d'exécution du système d'exploitation Windows avec des paramètres de chaine de caractères étendue (Unicode).
InSendMessage | BOOL InSendMessage(VOID); | user32.dll | Cette fonction permet de spécifier si la procédure de la fenêtre courante est en traitement d'un message d'envoi d'une autre tâche par appel de la fonction SendMessage.
InsertMenuA | BOOL InsertMenuA(HMENU hMenu, UINT uPosition, UINT uFlags, UINT uIDNewItem, LPCTSTR lpNewItem); | user32.dll | Cette fonction permet d'insérer un nouvel item de menu dans un menu et déplace les autres items en bas du menu.
InsertMenuItemA | BOOL InsertMenuItemA(HMENU hMenu, UINT uItem, BOOL fByPosition, LPMENUITEMINFO lpmii); | user32.dll | Cette fonction permet d'insérer un nouvel item de menu à la position spécifié dans la barre de menu ou un menu contextuel.
InsertMenuItemW | BOOL InsertMenuItemW(HMENU hMenu, UINT uItem, BOOL fByPosition, LPCMENUITEMINFOW lpmii); | user32.dll | Cette fonction permet d'insérer un nouvel item de menu à la position spécifié dans la barre de menu ou un menu contextuel avec les paramètres de chaine de caractères étendues (Unicode).
InsertMenuW | BOOL InsertMenuW(HMENU hMenu, UINT uPosition, UINT uFlags, UINT uIDNewItem, LPCWSTR lpNewItem); | user32.dll | Cette fonction permet d'insérer un nouvel item de menu dans un menu et déplace les autres items en bas du menu avec des paramètres de chaine de caractères étendues (Unicode).
InstallNewDevice | BOOL InstallNewDevice( HWND hwndParent, LPGUID ClassGuid, PDWORD pReboot ); | newdev.dll | Cette fonction permet d'installer un nouveau périphérique.
InterlockedCompareExchange | PVOID InterlockedCompareExchange(PVOID *Destination, PVOID Exchange, PVOID Comperand); | kernel32.dll | Cette fonction permet d'effectuer un traitement de comparaison atomique de valeurs spécifiés et échange les valeurs en se basant sur le résultat de la comparaison.
InterlockedDecrement | LONG InterlockedDecrement(LPLONG lpAddend); | kernel32.dll | Cette fonction permet de décrémenter les deux valeurs spécifié de un et vérifie le résultat des valeurs.
InterlockedExchange | LONG InterlockedExchange(LPLONG Target, LONG Value); | kernel32.dll | Cette fonction permet d'échanger automatiquement une paire de valeurs 32 bits.
InterlockedExchangeAdd | LONG InterlockedExchangeAdd(PLONG Addend, LONG Increment); | kernel32.dll | Cette fonction permet d'effectuer un traitement automatique d'ajouter d'une valeur incrémenté à une variable ajouté.
InterlockedIncrement | LONG InterlockedIncrement(LPLONG lpAddend); | kernel32.dll | Cette fonction permet d'incrémenter les deux valeurs des variables spécifiés et vérifie la valeur du résultat.
InternetAutodial | BOOL InternetAutodial(DWORD dwFlags,HWND hwndParent); | wininet.dll | Cette fonction permet de provoquer une connexion Internet en utilisant le protocole TCP/IP.
InternetAutodialHangup | BOOL InternetAutodialHangup(DWORD dwReserved); | wininet.dll | Cette fonction permet d'arrêter une connexion Internet en utilisant le protocole TCP/IP.
InternetGetConnectedState | BOOL InternetGetConnectedState(LPDWORD lpdwFlags,DWORD dwReserved); | wininet.dll | Cette fonction permet de demander le type de connexion Internet qu'utilise le TCP/IP de Windows.
IntersectClipRect | int IntersectClipRect(HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect); | gdi32.dll | Cette fonction permet de créer une nouvelle région de capture vers l'intersection de la région courante et du rectangle spécifié.
IntersectRect | BOOL IntersectRect(LPRECT lprcDst, CONST RECT * lprcSrc1, CONST RECT * lprcSrc2); | user32.dll | Cette fonction permet de calculer l'intersection de 2 sources rectangulaires et place les coordonnées du rectangle d'intersection dans le rectangle de destination.
InvalidateRect | BOOL InvalidateRect(HWND hWnd, CONST RECT * lpRect, BOOL bErase); | user32.dll | Cette fonction permet d'ajouter un rectangle pour la mise à jour d'une région d'une fenêtre.
InvalidateRgn | BOOL InvalidateRgn(HWND hWnd, HRGN hRgn, BOOL bErase); | user32.dll | Cette fonction permet d'ajouter une région à une région de fenêtre à mettre à jour.
InvertRect | BOOL InvertRect(HDC hDC, CONST RECT * lprc); | user32.dll | Cette fonction permet d'inverser une région rectangulaire.
InvertRgn | BOOL InvertRgn(HDC hdc, HRGN hrgn); | gdi32.dll | Cette fonction permet d'inverser les couleurs d'une région spécifié.
IsBadCodePtr | BOOL IsBadCodePtr(FARPROC lpfn); | kernel32.dll | Cette fonction permet de demander si l'appel du processus a un accès de mémoire en lecture à l'adresse spécifié.
IsBadHugeReadPtr | BOOL IsBadHugeReadPtr(CONST VOID *lp, UINT ucb); | kernel32.dll | Cette fonction permet de vérifier si l'appel du processus a un accès en lecture dans les rangs de mémoire spécifié.
IsBadHugeWritePtr | BOOL IsBadHugeWritePtr(LPVOID lp, UINT ucb); | kernel32.dll | Cette fonction permet de vérifier si l'appel du processus a un accès en écriture dans les rangs de mémoire spécifié.
IsBadReadPtr | BOOL IsBadReadPtr(CONST VOID *lp, UINT ucb); | kernel32.dll | Cette fonction permet de vérifier si l'appel du processus a un accès en lecture dans les rangs de mémoire spécifié.
IsBadStringPtrA | BOOL IsBadStringPtrA(LPCTSTR lpsz, UINT ucchMax); | kernel32.dll | Cette fonction permet de vérifier que l'appel du processus à un accès en lecture dans les rangs de mémoire pointer sur une chaine de caractères.
IsBadStringPtrW | BOOL IsBadStringPtrW(LPCWSTR str, UINT max); | kernel32.dll | Cette fonction permet de vérifier que l'appel du processus à un accès en lecture dans les rangs de mémoire pointer sur une chaine de caractères étendue (Unicode).
IsBadWritePtr | BOOL IsBadWritePtr(LPVOID lp, UINT ucb); | kernel32.dll | Cette fonction permet de vérifier si l'appel du processus a un accès en écriture dans les rangs de mémoire spécifié.
IsDBCSLeadByte | BOOL IsDBCSLeadByte(BYTE TestChar); | kernel32.dll | Cette fonction permet de demander si un caractère spécifié est un octet de tête, le premier octet d'un caractère est un DBCS (Double-Byte Character Set).
IsDBCSLeadByteEx | BOOL IsDBCSLeadByteEx(UINT CodePage, BYTE TestChar); | kernel32.dll | Cette fonction permet de demander si un caractère spécifié est un octet de tête, le premier octet d'un caractère est un DBCS (Double-Byte Character Set) avec un code de page.
IsCharAlphaA | BOOL IsCharAlphaA(TCHAR ch); | user32.dll | Cette fonction permet de demander si un caractère fait partie de l'ensemble de caractères alphabéthique de la langue.
IsCharAlphaNumericA | BOOL IsCharAlphaNumericA(TCHAR ch); | user32.dll | Cette fonction permet de demander si un caractère est une lettre ou un chiffre arabes.
IsCharAlphaNumericW | BOOL IsCharAlphaNumericW(WCHAR ch); | user32.dll | Cette fonction permet de demander si un caractère est une lettre ou un chiffre arabes avec des paramètres de chaine de caractères étendues (Unicode).
IsCharAlphaW | BOOL IsCharAlphaW(WCHAR ch); | user32.dll | Cette fonction permet de demander si un caractère fait partie de l'ensemble de caractères alphabéthique de la langue avec des paramètres de chaine de caractères étendues (Unicode).
IsCharLowerA | BOOL IsCharLowerA(TCHAR ch); | user32.dll | Cette fonction permet de demander si un caractère est une lettre en minuscule.
IsCharLowerW | BOOL IsCharLowerW(WCHAR ch); | user32.dll | Cette fonction permet de demander si un caractère est une lettre en minuscule avec des paramètres de chaine de caractères étendues (Unicode).
IsCharUpperA | BOOL IsCharUpperA(TCHAR ch); | user32.dll | Cette fonction permet de demander si un caractère est une lettre en majuscule.
IsCharUpperW | BOOL IsCharUpperW(WCHAR ch); | user32.dll | Cette fonction permet de demander si un caractère est une lettre en majuscule avec des paramètres de chaine de caractères étendues (Unicode).
IsChild | BOOL IsChild(HWND hWndParent, HWND hWnd); | user32.dll | Cette fonction permet de demander si la fenêtre spécifié est l'enfant ou d'une autre descendance direct de la fenêtre parent spécifié.
IsClipboardFormatAvailable | BOOL IsClipboardFormatAvailable(UINT format); | user32.dll | Cette fonction permet de demander si les données d'un certain format exist dans le presse-papier.
IsDebuggerPresent | BOOL IsDebuggerPresent(void); | kernel32.dll | Cette fonction permet de demander si l'application en cours est sous l'influence d'un débogueur.
IsDialogMessageA | BOOL IsDialogMessageA(HWND hDlg, LPMSG lpMsg); | user32.dll | Cette fonction permet de demander si le message spécifié est prévu dans la modélisation de la boite de dialogue donnée et, si c'est le cas, traite le message.
IsDialogMessageW | BOOL IsDialogMessageW(HWND hDlg, LPMSG lpMsg); | user32.dll | Cette fonction permet de demander si le message spécifié est prévu dans la modélisation de la boite de dialogue donnée et, si c'est le cas, traite le message avec des paramètres de chaine de caractères étendues (Unicode).
IsDlgButtonChecked | UINT IsDlgButtonChecked(HWND hDlg, int nIDButton); | user32.dll | Cette fonction permet de demander si un bouton a une coche à côté de lui et si un bouton à trois états de gris, est coché ou pas.
IsIconic | BOOL IsIconic(HWND hWnd); | user32.dll | Cette fonction permet de demander si la fenêtre spécifié est minimisé (en état d'icône).
IsMenu | BOOL IsMenu(HMENU hMenu); | user32.dll | Cette fonction permet d'indiquer si le Handle spécifié est un Handle de menu.
IsProcessorFeaturePresent | BOOL IsProcessorFeaturePresent(DWORD ProcessorFeature); | kernel32.dll | Cette fonction permet de demander si les fonctionnalités du microprocessseur sont supportés par l'un des microprocesseur de la machine courante.
IsRectEmpty | BOOL IsRectEmpty(CONST RECT * lprc); | user32.dll | Cette fonction permet de déterminer si le rectangle spécifié est vide.
IsTextUnicode | DWORD IsTextUnicode( CONST LPVOID lpBuffer, int cb, LPINT lpi ); | advapi32.dll | Cette fonction permet de demander l'adresse du tampon contenant probablement une forme de texte Unicode.
IsValidAcl | BOOL IsValidAcl( PACL pAcl ); | advapi32.dll | Cette fonction permet de valider une liste de contrôle d'accès (ACL).
IsValidCodePage | BOOL IsValidCodePage(UINT CodePage); | kernel32.dll | Cette fonction permet d'indiquer si la page de code spécifié est valide.
IsValidLocale | BOOL IsValidLocale(LCID Locale, DWORD dwFlags); | kernel32.dll | Cette fonction permet d'appliquer un test de validité à un identificateur locale.
IsValidSecurityDescriptor | BOOL IsValidSecurityDescriptor( PSECURITY_DESCRIPTOR pSecurityDescriptor ); | advapi32.dll | Cette fonction permet de valider une structure de descripteur de sécurité «SECURITY_DESCRIPTOR».
IsValidSid | BOOL IsValidSid( PSID pSid ); | advapi32.dll | Cette fonction permet de valider une structure SID en vérifiant que le numéro de version est situé dans le rang et les intervalles de nombres de sous-autorité.
IsWindow | BOOL IsWindow(HWND hWnd); | user32.dll | Cette fonction permet de demander si la fenêtre de Handle spécifié est valide.
IsWindowEnabled | BOOL IsWindowEnabled(HWND hWnd); | user32.dll | Cette fonction permet de demander si la fenêtre spécifié est activé par la souris ou l'entrée clavier.
IsWindowUnicode | BOOL IsWindowUnicode(HWND hWnd); | user32.dll | Cette fonction permet de demander si la fenêtre spécifié est une fenêtre Unicode native.
IsWindowVisible | BOOL IsWindowVisible(HWND hWnd); | user32.dll | Cette fonction permet de demander si la fenêtre spécifié est dans un état de visibilité.
IsZoomed | BOOL IsZoomed(HWND hWnd); | user32.dll | Cette fonction permet de demander si la fenêtre spécifié est maximisé (ou prend toute l'écran).
keybd_event | VOID keybd_event(BYTE bVk, BYTE bScan, DWORD dwFlags, DWORD dwExtraInfo); | user32.dll | Cette fonction permet de provoquer un événement d'appui de touche clavier.
KillTimer | BOOL KillTimer(HWND hWnd, UINT uIDEvent); | user32.dll | Cette fonction permet de détruire la minuterie spécifié.
LCMapStringA | int LCMapStringA(LCID Locale, DWORD dwMapFlags, LPCTSTR lpSrcStr, int cchSrc, LPTSTR lpDestStr, int cchDest); | kernel32.dll | Cette fonction permet de cartographier une chaine de caractères vers une autre en effectuant le traitement de transformation de dépendance locale.
LCMapStringW | INT LCMapStringW(LCID lcid, DWORD flags, LPCWSTR src, INT srclen, LPWSTR dst, INT dstlen); | kernel32.dll | Cette fonction permet de cartographier une chaine de caractères étendue (Unicode) vers une autre en effectuant le traitement de transformation de dépendance locale.
LeaveCriticalSection | VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection); | kernel32.dll | Cette fonction permet de libérer le propriétaire d'un objet de section critique spécifié.
LineDDA | BOOL LineDDA(int nXStart, int nYStart, int nXEnd, int nYEnd, LINEDDAPROC lpLineFunc, LPARAM lpData); | gdi32.dll | Cette fonction permet de calculer tous les points successifs d'une ligne spécifié par les coordonnées de début et de fin.
LineTo | BOOL LineTo(HDC hdc, int nXEnd, int nYEnd); | gdi32.dll | Cette fonction permet de tracer une ligne des coordonnées courante à la position spécifié.
LoadAcceleratorsA | HACCEL LoadAcceleratorsA(HINSTANCE hInstance, LPCTSTR lpTableName); | user32.dll | Cette fonction permet d'effectuer le chargement d'une table d'accélérateur d'une ressource.
LoadAcceleratorsW | HACCEL LoadAcceleratorsW(HINSTANCE hInstance, LPCWSTR lpTableName); | user32.dll | Cette fonction permet d'effectuer le chargement d'une table d'accélérateur d'une ressource avec des paramètres de chaine de caractères étendues (Unicode).
LoadBitmapA | HBITMAP LoadBitmapA(HINSTANCE hInstance, LPCTSTR lpBitmapName); | user32.dll | Cette fonction permet de charger la ressource de Bitmap spécifié du fichier de module d'exécutable donnée.
LoadBitmapW | HBITMAP LoadBitmapW(HINSTANCE hInstance, LPCWSTR lpBitmapName); | user32.dll | Cette fonction permet de charger la ressource de Bitmap spécifié du fichier de module d'exécutable donnée avec des paramètres de chaine de caractères étendues (Unicode).
LoadCursorA | HCURSOR LoadCursorA(HINSTANCE hInstance, LPCTSTR lpCursorName); | user32.dll | Cette fonction permet de sélectionner un pointeur de souris.
LoadCursorFromFileA | HCURSOR LoadCursorFromFileA(LPCTSTR lpFileName); | user32.dll | Cette fonction permet de charger à partir d'un fichier un pointeur de souris.
LoadCursorFromFileW | HCURSOR LoadCursorFromFileW(LPCWSTR lpFileName); | user32.dll | Cette fonction permet de charger à partir d'un fichier un pointeur de souris avec des paramètres de chaine de caractères étendues (Unicode).
LoadCursorW | HCURSOR LoadCursorW(HINSTANCE hInstance, LPCWSTR lpCursorName); | user32.dll | Cette fonction permet de sélectionner un pointeur de souris avec des paramètres de chaine de caractères étendues (Unicode).
LoadIconA | HICON LoadIconA(HINSTANCE hInstance, LPCTSTR lpIconName); | user32.dll | Cette fonction permet de charger une icône à partir d'un fichier.
LoadIconW | HICON LoadIconW(HINSTANCE hInstance, LPCWSTR lpIconName); | user32.dll | Cette fonction permet de charger une icône à partir d'un fichier avec des paramètres de chaine de caractères étendues (Unicode).
LoadImageA | HANDLE LoadImageA( HINSTANCE hinst, LPCTSTR lpszName, UINT uType, int cxDesired, int cyDesired, UINT fuLoad ); | user32.dll | Cette fonction permet de charger une image à partir d'une icône, un curseur ou un Bitmap.
LoadImageW | HANDLE LoadImageW(HINSTANCE hinst, LPCWSTR lpszName, UINT uType, int cxDesired, int cyDesired, UINT fuLoad); | user32.dll | Cette fonction permet de charger une image à partir d'une icône, un curseur ou un Bitmap avec des paramètres de chaine de caractères étendues (Unicode).
LoadKeyboardLayoutA | HKL LoadKeyboardLayoutA(LPCTSTR pwszKLID, UINT Flags); | user32.dll | Cette fonction permet de charger une nouvelle couche clavier dans le système.
LoadKeyboardLayoutW | HKL LoadKeyboardLayoutW(LPCWSTR pwszKLID, UINT Flags); | user32.dll | Cette fonction permet de charger une nouvelle couche clavier dans le système avec des paramètres de chaine de caractères étendues (Unicode).
LoadLibraryA | HINSTANCE LoadLibraryA(LPCTSTR lpLibFileName); | kernel32.dll | Cette fonction permet d'effectuer le chargement du module de bibliothèque spécifié.
LoadLibraryW | HMODULE LoadLibraryW(LPCWSTR libnameW); | kernel32.dll | Cette fonction permet d'effectuer le chargement du module de bibliothèque spécifié avec des paramètres de chaine de caractères étendue (Unicode).
LoadLibraryExA | HINSTANCE LoadLibraryExA(LPCTSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); | kernel32.dll | Cette fonction permet de cartographier un module exécutable spécifié dans l'espace d'adressage du processus d'appel.
LoadLibraryExW | HMODULE LoadLibraryExW(LPCWSTR libnameW, HANDLE hfile, DWORD flags); | kernel32.dll | Cette fonction permet de cartographier un module exécutable spécifié dans l'espace d'adressage du processus d'appel avec des paramètres de chaine de caractères étendue (Unicode).
LoadMenuA | HMENU LoadMenuA(HINSTANCE hInstance, LPCTSTR lpMenuName); | user32.dll | Cette fonction permet d'effectuer le chargement de la ressource de menu spécifié du fichier exécutable avec l'instance d'application donnée.
LoadMenuIndirectA | HMENU LoadMenuIndirectA(CONST MENUTEMPLATE *lpMenuTemplate); | user32.dll | Cette fonction permet de charger le gabarit de menu spécifié en mémoire.
LoadMenuIndirectW | HMENU LoadMenuIndirectW(CONST MENUTEMPLATE *lpMenuTemplate); | user32.dll | Cette fonction permet de charger le gabarit de menu spécifié en mémoire avec des paramètres de chaine de caractères étendues (Unicode).
LoadMenuW | HMENU LoadMenuW(HINSTANCE hInstance, LPCWSTR lpMenuName); | user32.dll | Cette fonction permet d'effectuer le chargement de la ressource de menu spécifié du fichier exécutable avec l'instance d'application donnée avec des paramètres de chaine de caractères étendues (Unicode).
LoadModule | DWORD LoadModule(LPCSTR lpModuleName, LPVOID lpParameterBlock); | kernel32.dll | Cette fonction permet de charger et d'exécution une application Windows ou crée un nouvel instance dans un application Windows existante.
LoadResource | HGLOBAL LoadResource(HMODULE hModule, HRSRC hResInfo); | kernel32.dll | Cette fonction permet de charger la ressource spécifié dans la mémoire global.
LoadStringA | int LoadStringA(HINSTANCE hInstance, UINT uID, LPTSTR lpBuffer, int nBufferMax); | user32.dll | Cette fonction permet d'effectuer le chargement de la chaine de caractères spécifié d'une ressource.
LoadStringW | int LoadStringW(HINSTANCE hInstance, UINT uID, LPWSTR lpBuffer, int nBufferMax); | user32.dll | Cette fonction permet d'effectuer le chargement de la chaine de caractères spécifié d'une ressource avec des paramètres de chaine de caractères étendues (Unicode).
LocalAlloc | HLOCAL LocalAlloc(UINT uFlags, UINT uBytes); | kernel32.dll | Cette fonction permet d'allouer le nombre d'octets spécifiés pour le tas local.
LocalCompact | UINT LocalCompact(UINT uMinFree); | kernel32.dll | Cette fonction permet de réarranger le tas local si la quantité de mémoire spécifié est disponible.
LocalFileTimeToFileTime | BOOL LocalFileTimeToFileTime(CONST FILETIME * lpLocalFileTime, LPFILETIME lpFileTime); | kernel32.dll | Cette fonction permet de convertir un temps de fichier local en temps de fichier basé sur l'UTC (Coordinated Universal Time).
LocalFlags | UINT LocalFlags(HLOCAL hMem); | kernel32.dll | Cette fonction permet de demander les informations à propos de l'objet de mémoire local.
LocalFree | HLOCAL LocalFree(HLOCAL hMem); | kernel32.dll | Cette fonction permet de demander l'objet de mémoire local et d'invalider un Handle.
LocalLock | LPVOID LocalLock(HLOCAL hMem); | kernel32.dll | Cette fonction permet de demander un pointeur sur l'objet de mémoire local.
LocalReAlloc | HLOCAL LocalReAlloc(HLOCAL hMem, UINT uBytes, UINT uFlags); | kernel32.dll | Cette fonction permet de changer la taille ou les attributs d'un objet de mémoire local spécifié.
LocalShrink | UINT LocalShrink(HLOCAL hloc, UINT cbNewSize); | kernel32.dll | Cette fonction rapetisser le tas local du segment spécifié.
LocalSize | UINT LocalSize(HLOCAL hMem); | kernel32.dll | Cette fonction permet de demander la taille, en octets, de l'objet de mémoire local spécifié.
LocalUnlock | BOOL LocalUnlock(HLOCAL hMem); | kernel32.dll | Cette fonction permet de déverrouiller un objet de mémoire local spécifié.
LockFile | BOOL LockFile(HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh, DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh); | kernel32.dll | Cette fonction permet de verrouiller une région d'un fichier ouvert.
LockFileEx | BOOL LockFileEx(HANDLE hFile, DWORD dwFlags, DWORD dwReserved, DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh, LPOVERLAPPED lpOverlapped ); | kernel32.dll | Cette fonction permet de verrouiller un rang d'octets dans un fichier ouvert avec un accès de partage ou d'exclusivité.
LockResource | LPVOID LockResource(HGLOBAL hResData); | kernel32.dll | Cette fonction permet de verrouiller une ressource spécifié.
LockWindowUpdate | BOOL LockWindowUpdate(HWND hWndLock); | user32.dll | Cette fonction permet d'activer ou désactiver l'affichage du contenu d'une fenêtre d'application.
LockWorkStation | BOOL LockWorkStation(VOID); | user32.dll | Cette fonction permet verrouiller la poste de travail avec le mot de passe de l'utilisateur courant. Cette fonction est l'équivalent de Ctrl+Alt+Delete et «Verrouiller Station de Travail».
LogonUserA | BOOL LogonUserA( LPTSTR lpszUsername, LPTSTR lpszDomain, LPTSTR lpszPassword, DWORD dwLogonType, DWORD dwLogonProvider, PHANDLE phToken ); | advapi32.dll | Cette fonction permet d'effectuer une opération de connexion d'un utilisateur.
LogonUserW | BOOL LogonUserW(LPWSTR lpszUsername, LPWSTR lpszDomain, LPWSTR lpszPassword, DWORD dwLogonType, DWORD dwLogonProvider, PHANDLE phToken ); | advapi32.dll | Cette fonction permet d'effectuer une opération de connexion d'un utilisateur avec des paramètres de chaine de caractères étendue (Unicode).
LookupAccountNameA | BOOL LookupAccountNameA( LPCTSTR lpSystemName, LPCTSTR lpAccountName, PSID Sid, LPDWORD cbSid, LPTSTR ReferencedDomainName, LPDWORD cbReferencedDomainName, PSID_NAME_USE peUse ); | advapi32.dll | Cette fonction permet d'accepter le nom d'un système ou d'un compte en entrée.
LookupAccountNameW | BOOL LookupAccountNameW( LPCWSTR lpSystemName, LPCWSTR lpAccountName, PSID Sid, LPDWORD cbSid, LPWSTR ReferencedDomainName, LPDWORD cchReferencedDomainName, PSID_NAME_USE peUse ); | advapi32.dll | Cette fonction permet d'accepter le nom d'un système ou d'un compte en entrée avec des paramètres de chaine de caractères étendue (Unicode).
LookupAccountSidA | BOOL LookupAccountSidA( LPCTSTR lpSystemName, PSID Sid, LPTSTR Name, LPDWORD cbName, LPTSTR ReferencedDomainName, LPDWORD cbReferencedDomainName, PSID_NAME_USE peUse ); | advapi32.dll | Cette fonction permet d'accepter un identificateur de sécurité (SID) en entrée
LookupAccountSidW | BOOL LookupAccountSidW( LPCWSTR lpSystemName, PSID Sid, LPWSTR Name, LPDWORD cbName, LPWSTR ReferencedDomainName, LPDWORD cbReferencedDomainName, PSID_NAME_USE peUse); | advapi32.dll | Cette fonction permet d'accepter un identificateur de sécurité (SID) en entrée avec des paramètres de chaine de caractères étendue (Unicode).
LookupIconIdFromDirectory | int LookupIconIdFromDirectory(PBYTE presbits, BOOL fIcon); | user32.dll | Cette fonction permet d'effectuer la recherche de données d'icône ou de curseur dans un icône ou curseur étant la meilleure correspondance avec le périphérique d'affichage courant.
LookupIconIdFromDirectoryEx | int LookupIconIdFromDirectoryEx(PBYTE presbits, BOOL fIcon, int cxDesired, int cyDesired, UINT Flags); | user32.dll | Cette fonction permet d'effectuer la recherche de données d'icône ou de curseur dans un icône ou curseur étant la meilleure correspondance avec le périphérique d'affichage courant avec des options particulières.
LookupPrivilegeDisplayNameA | BOOL LookupPrivilegeDisplayNameA( LPCTSTR lpSystemName, LPCTSTR lpName, LPTSTR lpDisplayName, LPDWORD cbDisplayName, LPDWORD lpLanguageId ); | advapi32.dll | Cette fonction permet de demander le nom d'affichage représenté par le prévilège spécifié.
LookupPrivilegeDisplayNameW | BOOL LookupPrivilegeDisplayNameW ( LPCWSTR lpSystemName, LPCWSTR lpName, LPWSTR lpDisplayName, LPDWORD cbDisplayName, LPDWORD lpLanguageId ); | advapi32.dll | Cette fonction permet de demander le nom d'affichage représenté par le prévilège spécifié avec des paramètres de chaine de caractères étendue (Unicode).
LookupPrivilegeNameA | BOOL LookupPrivilegeNameA( LPCTSTR lpSystemName, PLUID lpLuid, LPTSTR lpName, LPDWORD cbName ); | advapi32.dll | Cette fonction permet de demander le nom correspondant au prévilège représenté par un système spécifié par identificateur local unique (LUID).
LookupPrivilegeNameW | BOOL LookupPrivilegeNameW ( LPCWSTR lpSystemName, PLUID lpLuid, LPWSTR lpName, LPDWORD cchName ); | advapi32.dll | Cette fonction permet de demander le nom correspondant au prévilège représenté par un système spécifié par identificateur local unique (LUID) avec des paramètres de chaine de caractères étendue (Unicode)
LookupPrivilegeValueA | BOOL LookupPrivilegeValueA( LPCTSTR lpSystemName, LPCTSTR lpName, PLUID lpLuid ); | advapi32.dll | Cette fonction permet de demander l'identificateur local unique (LUID) utilisé par un système spécifié par une représentation local d'un nom de prévilège.
LookupPrivilegeValueW | BOOL LookupPrivilegeValueW( LPCWSTR lpSystemName, LPCWSTR lpName, PLUID lpLuid ); | advapi32.dll | Cette fonction permet de demander l'identificateur local unique (LUID) utilisé par un système spécifié par une représentation local d'un nom de prévilège avec des paramètres de chaine de caractères étendue (Unicode)
LPtoDP | BOOL LPtoDP(HDC hdc, LPPOINT lpPoints, int nCount); | gdi32.dll | Cette fonction permet de convertir les coordonnées logiques (points) en coordonnées d'un périphérique.
lstrcatA | LPTSTR lstrcatA(LPTSTR lpString1, LPCTSTR lpString2); | kernel32.dll | Cette fonction permet d'ajouter une chaine de caractères à une autre.
lstrcatW | LPWSTR lstrcatW(LPWSTR lpString1, LPWSTR lpString2); | kernel32.dll | Cette fonction permet d'ajouter une chaine de caractères étendue (Unicode) à une autre.
lstrcmpA | int lstrcmpA(LPCTSTR lpString1, LPCTSTR lpString2); | kernel32.dll | Cette fonction permet d'effectuer la comparaison de deux chaines de caractères ASCIZ.
lstrcmpW | int lstrcmpW(LPCWSTR str1, LPCWSTR str2); | kernel32.dll | Cette fonction permet d'effectuer la comparaison de deux chaines de caractères étendues (Unicode).
lstrcmpiA | int lstrcmpiA(LPCTSTR lpString1, LPCTSTR lpString2); | kernel32.dll | Cette fonction permet d'effectuer une comparaison de deux chaines de caractères sans tenir compte des majuscules ou minuscules.
lstrcmpiW | int lstrcmpiW(LPCWSTR str1, LPCWSTR str2); | kernel32.dll | Cette fonction permet d'effectuer une comparaison de deux chaines de caractères étendues (Unicode) sans tenir compte des majuscules ou minuscules.
lstrcpyA | LPTSTR lstrcpyA(LPTSTR lpString1, LPCTSTR lpString2); | kernel32.dll | Cette fonction permet de copier une chaine de caractères dans un tampon.
lstrcpyW | LPCWSTR lstrcpyW(LPCWSTR lpString1, LPCWSTR lpString2); | kernel32.dll | Cette fonction permet de copier une chaine de caractères étendues (Unicode) dans un tampon.
lstrcpynA | LPTSTR lstrcpynA(LPTSTR lpString1, LPCTSTR lpString2, int iMaxLength); | kernel32.dll | Cette fonction permet d'effectuer une copie du nombre de caractères spécifié d'une chaine de caractères vers un tampon en mémoire.
lstrcpynW | LPCWSTR lstrcpynW(LPCWSTR lpString1, LPCWSTR lpString2, int iMaxLength); | kernel32.dll | Cette fonction permet d'effectuer une copie du nombre de caractères spécifié d'une chaine de caractères étendue (Unicode) vers un tampon en mémoire.
lstrlenA | int lstrlenA(LPCTSTR lpString); | kernel32.dll | Cette fonction permet de retourner la longueur, en octets, de la chaine de caractères spécifié.
lstrlenW | int lstrlenW(LPCWSTR lpString); | kernel32.dll | Cette fonction permet de retourner la longueur, en octets, de la chaine de caractères étendue (Unicode) spécifié.
LZClose | VOID LZClose(INT hFile); | lz32.dll | Cette fonction permet de fermer un fichier compressé.
LZCopy | LONG LZCopy(INT hfSource,INT hfDest); | lz32.dll | Cette fonction permet de copier un fichier en dehors du fichier compressé ou permet de le compresser et de l'inclure avec le fichier compressé.
LZOpenFileA | INT LZOpenFileA(LPTSTR lpFileName,LPOFSTRUCT lpReOpenBuf, WORD wStyle); | lz32.dll | Cette fonction permet de créer, ouvrir ou supprimer un fichier compressé.
MakeAbsoluteSD | BOOL MakeAbsoluteSD( PSECURITY_DESCRIPTOR pSelfRelativeSecurityDescriptor, PSECURITY_DESCRIPTOR pAbsoluteSecurityDescriptor, LPDWORD lpdwAbsoluteSecurityDescriptorSize, PACL pDacl, LPDWORD lpdwDaclSize, PACL pSacl, LPDWORD lpdwSaclSize, PSID pOwner, LPDWORD lpdwOwnerSize, PSID pPrimaryGroup, LPDWORD lpdwPrimaryGroupSize ); | advapi32.dll | Cette fonction permet de créer un descripteur de sécurité dans un format absolue en utilisant un gabarit de format relatif de descripteur de sécurité.
MakeSelfRelativeSD | BOOL MakeSelfRelativeSD( PSECURITY_DESCRIPTOR pAbsoluteSecurityDescriptor, PSECURITY_DESCRIPTOR pSelfRelativeSecurityDescriptor, LPDWORD lpdwBufferLength ); | advapi32.dll | Cette fonction permet de créer un descripteur de sécurité dans un format relatif en utilisant un gabarit de format absolue de descripteur de sécurité.
MakeSureDirectoryPathExists | BOOL MakeSureDirectoryPathExists(IN LPSTR Path); | imagehlp.dll | Cette fonction permet de s'assurer que toutes l'arborescente d'un chemin de répertoires existent et ont été créés.
MapDialogRect | BOOL MapDialogRect(HWND hDlg, LPRECT lpRect); | user32.dll | Cette fonction permet d'effectuer la conversion de l'unité de boite de dialogue à l'unité d'écran (pixels).
MapGenericMask | VOID MapGenericMask( PDWORD AccessMask, PGENERIC_MAPPING GenericMapping ); | advapi32.dll | Cette fonction permet de générer une carte de masque de bon accès spécifié et de bon accès standard
MAPISendDocuments | ULONG FAR PASCAL MAPISendDocuments(ULONG ulUIParam, LPTSTR lpszDelimChar, LPTSTR lpszFullPaths, LPTSTR lpszFileNames, ULONG ulReserved); | mapi32.dll | Cette fonction permet d'effectuer l'envoye d'un message de style standard avec des fichiers attachés.
MapViewOfFile | LPVOID MapViewOfFile(HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, DWORD dwNumberOfBytesToMap); | kernel32.dll | Cette fonction permet de cartographier une vue d'un fichier dans l'espace d'adressage d'un processus appelant.
MapViewOfFileEx | LPVOID MapViewOfFileEx(HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, DWORD dwNumberOfBytesToMap, LPVOID lpBaseAddress); | kernel32.dll | Cette fonction permet de cartographier une vue d'un fichier dans l'espace d'adressage d'un processus appelant avec un adresse de mémoire spécifié.
MapVirtualKeyA | UINT MapVirtualKeyA(UINT uCode, UINT uMapType); | user32.dll | Cette fonction permet d'effectuer la conversion du code de touche virtuel en une valeur de caractère et un code de balayage (scan code) ou convertie le code de balayage en code de touche virtuel.
MapVirtualKeyExA | UINT MapVirtualKeyExA(UINT uCode, UINT uMapType, HKL dwhkl); | user32.dll | Cette fonction permet d'effectuer la conversion du code de touche virtuel en une valeur de caractère et un code de balayage (scan code) ou convertie le code de balayage en code de touche virtuel en utilisant des options supplémentaires.
MapVirtualKeyExW | UINT MapVirtualKeyExW(UINT uCode, UINT uMapType, HKL dwhkl); | user32.dll | Cette fonction permet d'effectuer la conversion du code de touche virtuel en une valeur de caractère et un code de balayage (scan code) ou convertie le code de balayage en code de touche virtuel en utilisant des options supplémentaires avec des paramètres de chaine de caractères étendues (Unicode).
MapVirtualKeyW | UINT MapVirtualKeyW(UINT uCode, UINT uMapType); | user32.dll | Cette fonction permet d'effectuer la conversion du code de touche virtuel en une valeur de caractère et un code de balayage (scan code) ou convertie le code de balayage en code de touche virtuel avec des paramètres de chaine de caractères étendues (Unicode).
MapWindowPoints | int MapWindowPoints(HWND hWndFrom, HWND hWndTo, LPPOINT lpPoints, UINT cPoints); | user32.dll | Cette fonction permet de convertir (cartographié) un ensemble de points d'une espace de coordonnées relatif d'une fenêtre vers une autre fenêtre d'espace de coordonnées relatif.
MaskBlt | BOOL MaskBlt(HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, HDC hdcSrc, int nXSrc, int nYSrc, HBITMAP hbmMask, int xMask, int yMask, DWORD dwRop); | gdi32.dll | Cette fonction permet de fusionner les données de couleur du Bitmap de la source et la destination en utilisant les opérations de masque et de raster spécifié.
mciSendStringA | MCIERROR mciSendStringA(LPCTSTR lpszCommand, LPTSTR lpszReturnString, UINT cchReturn, HANDLE hwndCallback); | winmm.dll | Cette fonction permet d'envoyer des commandes au pilote matériel MCI s'occupant du multimédia.
MenuItemFromPoint | UINT MenuItemFromPoint(HWND hWnd, HMENU hMenu, POINT ptScreen); | user32.dll | Cette fonction permet de demander quel est l'item de menu situé à l'emplacement spécifié, peut importe lequel.
MessageBeep | BOOL MessageBeep(UINT uType); | user32.dll | Cette fonction permet de faire jouer un de ses sons standard du système d'exploitation Windows.
MessageBoxA | int MessageBoxA(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType); | user32.dll | Cette fonction permet d'afficher une boite de dialogue contenant un message et des boutons d'actions.
MessageBoxExA | int MessageBoxExA(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType, WORD wLanguageId); | user32.dll | Cette fonction permet de créer, d'afficher et d'effectuer des opérations sur une boite de message.
MessageBoxExW | int MessageBoxExW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType, WORD wLanguageId); | user32.dll | Cette fonction permet de créer, d'afficher et d'effectuer des opérations sur une boite de message avec des paramètres de chaine de caractères étendues (Unicode).
MessageBoxIndirectA | BOOL MessageBoxIndirectA(LPMSGBOXPARAMS lpMsgBoxParams); | user32.dll | Cette fonction permet de créer, d'afficher et d'effectuer des opérations sur une boite de message à l'aide d'une structure de données.
MessageBoxIndirectW | BOOL MessageBoxIndirectW(MSGBOXPARAMSW lpMsgBoxParams); | user32.dll | Cette fonction permet de créer, d'afficher et d'effectuer des opérations sur une boite de message à l'aide d'une structure de données avec des paramètres de chaine de caractères étendues (Unicode).
MessageBoxW | int MessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType); | user32.dll | Cette fonction permet d'afficher une boite de dialogue contenant un message et des boutons d'actions avec des paramètres de chaine de caractères étendues (Unicode).
ModifyMenuA | BOOL ModifyMenuA(HMENU hMnu, UINT uPosition, UINT uFlags, UINT uIDNewItem, LPCTSTR lpNewItem); | user32.dll | Cette fonction permet d'effectuer une modification sur un item de menu existant.
ModifyMenuW | BOOL ModifyMenuW(HMENU hMnu, UINT uPosition, UINT uFlags, UINT uIDNewItem, LPCWSTR lpNewItem); | user32.dll | Cette fonction permet d'effectuer une modification sur un item de menu existant avec des paramètres de chaine de caractères étendues (Unicode).
ModifyWorldTransform | BOOL ModifyWorldTransform(HDC hdc, CONST XFORM * lpXform, DWORD iMode); | gdi32.dll | Cette fonction permet de changer la transformation mondiale pour le périphérique de contexte utilisant le mode spécifié.
mouse_event | VOID mouse_event(DWORD dwFlags, DWORD dx, DWORD dy, DWORD cButtons, DWORD dwExtraInfo); | user32.dll | Cette fonction permet de provoquer un événement de déplacement de pointeur de souris ou d'actions de bouton de souris.
MoveFileA | BOOL MoveFileA(LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName); | kernel32.dll | Cette fonction permet de renommer ou déplacer un fichier ou un répertoire
MoveFileExA | BOOL MoveFileExA(LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName, DWORD dwFlags); | kernel32.dll | Cette fonction permet de renommer un fichier existant.
MoveFileExW | BOOL MoveFileExW(LPCWSTR source, LPCWSTR dest, DWORD flag); | kernel32.dll | Cette fonction permet de renommer un fichier existant avec un paramètre de chaine de caractères étendue (Unicode).
MoveFileW | BOOL MoveFileW(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName); | kernel32.dll | Cette fonction permet de renommer ou déplacer un fichier ou un répertoire avec un paramètre de chaine de caractères étendue (Unicode).
MoveToEx | BOOL MoveToEx(HDC hdc, int X, int Y, LPPOINT lpPoint); | gdi32.dll | Cette fonction permet de changer la position du pointeur graphique
MoveWindow | BOOL MoveWindow(HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint); | user32.dll | Cette fonction permet de changer l'emplacement d'une fenêtre d'application
MsgWaitForMultipleObjects | DWORD MsgWaitForMultipleObjects(DWORD nCount, LPHANDLE pHandles, BOOL fWaitAll, DWORD dwMilliseconds, DWORD dwWakeMask); | user32.dll | Cette fonction permet de retourner un objet spécifié suite à un état, un type spécifié étant disponible de l'entrée de la file d'attente d'un processus léger ou intervalle de dépassement de délai.
MsgWaitForMultipleObjectsEx | DWORD MsgWaitForMultipleObjectsEx(DWORD nCount, const HANDLE *pHandles, DWORD dwMilliseconds, DWORD dwWakeMask, DWORD dwFlags); | user32.dll | Cette fonction permet de retourner un objet spécifié suite à un état, un type spécifié étant disponible de l'entrée de la file d'attente d'un processus léger ou intervalle de dépassement de délai avec des paramètres supplémentaires.
MulDiv | int MulDiv(int nNumber, int nNumerator, int nDenominator); | kernel32.dll | Cette fonction permet de diviser le produit de deux entiers long
MultiByteToWideChar | int MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cchMultiByte, LPWSTR lpWideCharStr, int cchWideChar); | kernel32.dll | Cette fonction permet de convertir une chaine de caractères ASCII en chaine de caractères Unicode.
MultinetGetConnectionPerformanceA | DWORD MultinetGetConnectionPerformanceA(LPNETRESOURCE lpNetResource, LPNETCONNECTINFOSTRUCT lpNetConnectInfoStruct); | mpr.dll | Cette fonction permet de demander les informations à propos des performances attendu d'une connexion utilisé pour l'accès à une ressource de réseau.
MultinetGetConnectionPerformanceW | DWORD MultinetGetConnectionPerformanceW(LPNETRESOURCEW lpNetResource, LPNETCONNECTINFOSTRUCT lpNetConnectInfoStruct); | mpr.dll | Cette fonction permet de demander les informations à propos des performances attendu d'une connexion utilisé pour l'accès à une ressource de réseau avec des paramètres de chaine de caractères étendues (Unicode).
NotifyChangeEventLog | BOOL NotifyChangeEventLog( HANDLE hEventLog, HANDLE hEvent ); | advapi32.dll | Cette fonction permet de recevoir des avertissements d'application lorsqu'un événement écrit dans le fichier de journal de bord spécifié.
ObjectCloseAuditAlarmA | BOOL ObjectCloseAuditAlarmA( LPCTSTR SubsystemName, LPVOID HandleId, BOOL GenerateOnClose ); | advapi32.dll | Cette fonction permet de générer des messages dans le journal de bord quand un identificateur Handle d'objet est supprimé.
ObjectCloseAuditAlarmW | BOOL ObjectCloseAuditAlarmW(LPCWSTR SubsystemName, LPVOID HandleId, BOOL GenerateOnClose); | advapi32.dll | Cette fonction permet de généré des messages dans le journal de bord quand un identificateur Handle d'objet est supprimé avec des paramètres de chaine de caractères étendue (Unicode).
ObjectDeleteAuditAlarmA | BOOL ObjectDeleteAuditAlarmA(LPCSTR SubsystemName, LPVOID HandleId, BOOL GenerateOnClose); | advapi32.dll | Cette fonction permet de supprimer un message dans le journal de bord.
ObjectDeleteAuditAlarmW | BOOL ObjectDeleteAuditAlarmW(LPCWSTR SubsystemName,LPVOID HandleId,BOOL GenerateOnClose); | advapi32.dll | Cette fonction permet de supprimer un message dans le journal de bord avec des paramètres de chaine de caractères étendue (Unicode).
ObjectOpenAuditAlarmA | BOOL ObjectOpenAuditAlarmA( LPCTSTR SubsystemName, LPVOID HandleId, LPTSTR ObjectTypeName, LPTSTR ObjectName, PSECURITY_DESCRIPTOR pSecurityDescriptor, HANDLE ClientToken, DWORD DesiredAccess, DWORD GrantedAccess, PPRIVILEGE_SET Privileges, BOOL ObjectCreation, BOOL AccessGranted, LPBOOL GenerateOnClose ); | advapi32.dll | Cette fonction permet de générer des messages dans le journal de bord quand une application client tente d'obtenir l'accès à un objet ou dans créer un nouveau.
ObjectOpenAuditAlarmW | BOOL ObjectOpenAuditAlarmW(LPCWSTR SubsystemName, LPVOID HandleId, LPWSTR ObjectTypeName, LPWSTR ObjectName, PSECURITY_DESCRIPTOR pSecurityDescriptor, HANDLE ClientToken, DWORD DesiredAccess, DWORD GrantedAccess, PPRIVILEGE_SET Privileges, BOOL ObjectCreation, BOOL AccessGranted, LPBOOL GenerateOnClose ); | advapi32.dll | Cette fonction permet de générer des messages dans le journal de bord quand une application client tente d'obtenir l'accès à un objet ou dans créer un nouveau avec des paramètres de chaine de caractères étendue (Unicode).
ObjectPrivilegeAuditAlarmA | BOOL ObjectPrivilegeAuditAlarmA( LPCTSTR lpszSubsystem, LPVOID lpvHandleId, HANDLE hClientToken, DWORD dwDesiredAccess, PPRIVILEGE_SET pps, BOOL fAccessGranted ); | advapi32.dll | Cette fonction permet de générer un message dans le journal de bord quand un résultat d'un client est effectué avec des opérations de prévilège sur un serveur d'application déjà ouvert avec l'identificateur Handle de l'objet.
ObjectPrivilegeAuditAlarmW | BOOL ObjectPrivilegeAuditAlarmW( LPCWSTR SubsystemName, LPVOID HandleId, HANDLE ClientToken, DWORD DesiredAccess, PPRIVILEGE_SET Privileges, BOOL AccessGranted ); | advapi32.dll | Cette fonction permet de générer un message dans le journal de bord quand un résultat d'un client est effectué avec des opérations de prévilège sur un serveur d'application déjà ouvert avec l'identificateur Handle de l'objet avec des paramètres de chaine de caractères étendue (Unicode).
OemKeyScan | DWORD OemKeyScan(WORD wOemChar); | user32.dll | Cette fonction permet de demander le code ASCII OEM entre 0 et FFh situé dans les codes de balayage et des touches états (Ctrl, Alt et Shift).
OemToCharA | BOOL OemToCharA(LPCSTR lpszOemStr, LPTSTR lpszStr); | user32.dll | Cette fonction permet d'effectuer la conversion d'une chaine de caractères contenant un ensemble de caractères OEM vers du ANSI ou du Unicode.
OemToCharBuffA | BOOL OemToCharBuffA(LPCSTR lpOemStr, LPTSTR lpszStr, DWORD cchString); | user32.dll | Cette fonction permet d'effectuer la conversion d'un certain nombre de caractères OEM vers du ANSI ou du Unicode.
OemToCharBuffW | BOOL OemToCharBuffW(LPCSTR lpOemStr, LPWSTR lpszStr, DWORD cchString); | user32.dll | Cette fonction permet d'effectuer la conversion d'un certain nombre de caractères OEM vers du ANSI ou du Unicode avec des paramètres de chaine de caractères étendues (Unicode).
OemToCharW | BOOL OemToCharW(LPCSTR lpszOemStr, LPWSTR lpszStr); | user32.dll | Cette fonction permet d'effectuer la conversion d'une chaine de caractères contenant un ensemble de caractères OEM vers du ANSI ou du Unicode avec des paramètres de chaine de caractères étendues (Unicode).
OffsetClipRgn | int OffsetClipRgn(HDC hdc, int nXOffset, int nYOffset); | gdi32.dll | Cette fonction permet de déplacer la région de clip dans le périphérique spécifié et par le déplacement spécifié.
OffsetRect | BOOL OffsetRect(LPRECT lprc, int x, int y); | user32.dll | Cette fonction permet de déplacer le rectangle spécifié avec le nombre de déplacements spécifié.
OffsetRgn | int OffsetRgn(HRGN hrgn, int nXOffset, int nYOffset); | gdi32.dll | Cette fonction permet de déplacer la région spécifié par le déplacement spécifié.
OffsetViewportOrgEx | BOOL OffsetViewportOrgEx(HDC hdc, int nXOffset, int nYOffset, LPPOINT lpPoint); | gdi32.dll | Cette fonction permet de modifier le point de vue d'origine relatif pour un périphérique de contexte en utilisant le déplacement horizontal et vertical spécifié.
OffsetWindowOrgEx | BOOL OffsetWindowOrgEx(HDC hdc, int nXOffset, int nYOffset, LPPOINT lpPoint); | gdi32.dll | Cette fonction permet de modifier le point de vue de la fenêtre d'origine relatif de la valeur courante.
OpenBackupEventLogA | HANDLE OpenBackupEventLogA( LPCTSTR lpszUNCServerName, LPCTSTR lpszFileName ); | advapi32.dll | Cette fonction permet d'ouvir un identificateur Handle pour un événement de copie de sauvegarde du journal de bord.
OpenBackupEventLogW | HANDLE OpenBackupEventLogW( LPCWSTR lpUNCServerName, LPCWSTR lpFileName ); | advapi32.dll | Cette fonction permet d'ouvir un identificateur Handle pour un événement de copie de sauvegarde du journal de bord avec des paramètres de chaine de caractères étendue (Unicode).
OpenClipboard | BOOL OpenClipboard(HWND hwnd); | user32.dll | Cette fonction permet de commencer un traitement avec le presse-papier.
OpenDesktopA | HDESK OpenDesktopA(LPCTSTR lpszDesktopName, DWORD dwFlags, BOOL fInherit, DWORD dwDesiredAccess); | user32.dll | Cette fonction permet de demander le Handle d'un bureau existant.
OpenDesktopW | HDESK OpenDesktopW(LPWSTR lpszDesktopName, DWORD dwFlags, BOOL fInherit, DWORD dwDesiredAccess); | user32.dll | Cette fonction permet de demander le Handle d'un bureau existant avec des paramètres de chaine de caractères étendues (Unicode).
OpenEventA | HANDLE OpenEventA(DWORD fdwAccess, BOOL fInherit, LPCTSTR lpszEventName); | kernel32.dll | Cette fonction permet de demander un Handle dans objet existant d'événement nommé.
OpenEventW | HANDLE OpenEventW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpName); | kernel32.dll | Cette fonction permet de demander un Handle dans objet existant d'événement nommé avec des paramètres de chaines de caractères étendue (Unicode).
OpenEventLogA | HANDLE OpenEventLogA( LPCTSTR lpszUNCServerName, LPCTSTR lpszSourceName ); | advapi32.dll | Cette fonction permet d'ouvrir un identificateur Handle d'un événement de journal de bord.
OpenEventLogW | HANDLE OpenEventLogW( LPCWSTR uncname, LPCWSTR source ); | advapi32.dll | Cette fonction permet d'ouvrir un identificateur Handle d'un événement de journal de bord avec des paramètres de chaine de caractères étendue (Unicode).
OpenFile | HFILE OpenFile( LPCSTR lpszFile, LPOFSTRUCT lpOpenBuff, UINT fuMode ); | kernel32.dll | Cette fonction permet de créer, d'ouvrir, de réouvrir ou de supprimer un fichier.
OpenFileMappingA | HANDLE OpenFileMappingA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName); | kernel32.dll | Cette fonction permet d'ouvrir un objet de cartographie de fichier nommé.
OpenFileMappingW | HANDLE OpenFileMappingW(DWORD access, BOOL inherit, LPCWSTR name); | kernel32.dll | Cette fonction permet d'ouvrir un objet de cartographie de fichier nommé avec des paramètres de chaine de caractères étendue (Unicode).
OpenIcon | BOOL OpenIcon(HWND hwnd); | user32.dll | Cette fonction permet de passer en mode actif l'icône d'une fenêtre.
OpenInputDesktop | HDESK OpenInputDesktop(DWORD dwFlags, BOOL fInherit, DWORD dwDesiredAccess); | user32.dll | Cette fonction permet de demander le Handle d'un bureau reçevant une entrée utilisateur.
OpenMutexA | HANDLE OpenMutexA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName); | kernel32.dll | Cette fonction permet d'ouvrir un objet Mutex nommé.
OpenMutexW | HANDLE OpenMutexW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpName); | kernel32.dll | Cette fonction permet d'ouvrir un objet Mutex nommé avec des paramètres de chaine de caractères étendue (Unicode).
OpenPrinterA | BOOL OpenPrinterA(LPTSTR pPrinterName, LPHANDLE phPrinter, LPPRINTER_DEFAULTS pDefault); | winspool.drv | Cette fonction permet d'ouvrir la communication avec une imprimante.
OpenProcess | HANDLE OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId); | kernel32.dll | Cette fonction permet d'ouvrir un accès de jeton associé avec un processus.
OpenProcessToken | BOOL OpenProcessToken( HANDLE hProcess, DWORD dwAccessMask, PHANDLE phToken ); | advapi32.dll | Cette fonction permet d'ouvrir un accès de jeton associé avec un processus.
OpenSemaphoreA | HANDLE OpenSemaphoreA(DWORD fdwAccess, BOOL fInherit, LPCTSTR lpszSemName); | kernel32.dll | Cette fonction permet de retourner le Handle d'objet existant de sémaphore nommé.
OpenSemaphoreW | HANDLE OpenSemaphoreW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpName); | kernel32.dll | Cette fonction permet de retourner le Handle d'objet existant de sémaphore nommé avec des paramètres de chaine de caractères étendue (Unicode).
OpenThreadToken | BOOL OpenThreadToken( HANDLE hThread, DWORD dwAccessMask, BOOL fOpenAsSelf, PHANDLE phToken ); | advapi32.dll | Cette fonction permet d'ouvrir un accès de jeton associé avec un processus léger.
OpenWaitableTimerA | HANDLE OpenWaitableTimerA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpTimerName); | kernel32.dll | Cette fonction permet d'ouvrir une minuterie d'attente existante nommé.
OpenWaitableTimerW | HANDLE OpenWaitableTimerW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpTimerName); | kernel32.dll | Cette fonction permet d'ouvrir une minuterie d'attente existante nommé avec des paramètres de chaine de caractères étendue (Unicode).
OpenWindowStationA | HWINSTA OpenWindowStationA(LPCTSTR lpszWinStaName, BOOL fInherit, DWORD dwDesiredAccess); | user32.dll | Cette fonction permet de demander le Handle d'une fenêtre de station existante.
OpenWindowStationW | HWINSTA OpenWindowStationW(LPWSTR lpszWinStaName, BOOL fInherit, DWORD dwDesiredAccess); | user32.dll | Cette fonction permet de demander le Handle d'une fenêtre de station existante avec des paramètres de chaine de caractères étendues (Unicode).
OutputDebugStringA | VOID OutputDebugStringA(LPCTSTR lpszOutputString); | kernel32.dll | Cette fonction permet d'envoyer une chaine de caractères au débogueur de l'application courante.
OutputDebugStringW | VOID OutputDebugStringW(LPCWSTR str); | kernel32.dll | Cette fonction permet d'envoyer une chaine de caractères au débogueur de l'application courante avec des paramètres de chaine de caractères étendue (Unicode).
PackDDElParam | LONG PackDDElParam(UINT uMsg, UINT uLow, UINT uHigh); | user32.dll | Cette fonction permet de compacté la valeur DDE spécifié dans une structure interne utilisé dans le partage de données DDE entre le traitement de processus.
PaintDesktop | BOOL WINAPI PaintDesktop(HDC hdc); | user32.dll | Cette fonction permet de remplir le bureau avec une couleur ou un papier-peint.
PaintRgn | BOOL PaintRgn(HDC hdc, HRGN hrgn); | gdi32.dll | Cette fonction permet de remplir une région, en utilisant la brosse courante, pour un périphérique de contexte spécifié.
PatBlt | BOOL PatBlt(HDC hdc, int nXLeft, int nYLeft, int nWidth, int nHeight, DWORD dwRop); | gdi32.dll | Cette fonction permet de créer une palette de bits pour un périphérique spécifié.
PathAddBackslashA | LPSTR PathAddBackslashA(LPSTR lpszPath); | shlwapi.dll | Cette fonction permet d'ajouter une barre oblique inversé à la fin du chemin de répertoire spécifié.
PathAddExtension | BOOL PathAddExtension(LPTSTR pszPath, LPCTSTR pszExtension); | shlwapi.dll | Cette fonction permet d'ajouter une extension à nom de fichiers.
PathAppendA | BOOL PathAppendA(LPSTR lpszPath, LPCSTR lpszAppend); | shlwapi.dll | Cette fonction permet d'ajouter un chemin de répertoire à la fin d'un autre chemin de répertoire.
PathBuildRootA | LPSTR PathBuildRootA(LPSTR lpszPath, int drive); | shlwapi.dll | Cette fonction permet de créer un répertoire racine pour unité disque spécifié.
PathCanonicalizeA | BOOL PathCanonicalizeA( LPSTR lpszBuf, LPCSTR lpszPath); | shlwapi.dll | Cette fonction permet de transformer un chemin de répertoire de sa forme absolue à relative, c'est-à-dire de remplacer des "../" pour atteinte le répertoire en court par exemple.
PathCombine | LPTSTR PathCombine(LPTSTR lpszDest, LPCTSTR lpszDir, LPCTSTR lpszFile); | shlwapi.dll | Cette fonction permet d'ajouter un chemin de répertoire à la fin d'un autre chemin de répertoire.
PathCommonPrefix | int PathCommonPrefix(LPCTSTR pszFile1, LPCTSTR pszFile2, LPTSTR pszPath); | shlwapi.dll | Cette fonction permet d'effectuer la comparaison de deux répertoires et de vérifier les préfixes communs.
PathCompactPathA | BOOL PathCompactPathA( HDC hDC, LPSTR lpszPath, UINT dx); | shlwapi.dll | Cette fonction permet de réduire la longueur en pixel d'un chemin de répertoire en remplaçant la partie trop longue par des abréviations pour qu'il soit affichable à l'écran par exemple.
PathCompactPathExA | BOOL PathCompactPathExA(LPSTR lpszDest,LPCSTR lpszPath, UINT cchMax, DWORD dwFlags); | shlwapi.dll | Cette fonction permet de réduire la longueur en caractères d'un chemin de répertoire en remplaçant la partie trop longue par des abréviations pour qu'il soit affichable à l'écran par exemple.
PathCreateFromUrlA | HRESULT PathCreateFromUrlA( LPCTSTR pszUrl, LPTSTR pszPath, LPDWORD pcchPath, DWORD dwReserved); | shlwapi.dll | Cette fonction permet de convertir un URL sous sa forme du système d'exploitation de style DOS.
PathFindOnPathA | BOOL PathFindOnPathA(LPTSTR pszFile, LPCTSTR *ppszOtherDirs); | shlwapi.dll | Cette fonction permet d'effectuer la recherche d'un nom de fichier dans un répertoire.
PathGetDriveNumberA | int PathGetDriveNumberA(LPCTSTR lpsz); | shlwapi.dll | Cette fonction permet de demander le numéro de l'unité disque contenu dans un chemin de répertoire.
PathIsDirectoryEmptyA | BOOL PathIsDirectoryEmptyA(LPCTSTR pszPath); | shlwapi.dll | Cette fonction permet de demander si le répertoire spécifier est vide.
PathIsSystemFolder | BOOL PathIsSystemFolder(LPCTSTR pszPath, DWORD dwAttrb); | shlwapi.dll | Cette fonction permet de demander si le répertoire spécifier à l'attribut de dossier système.
PathIsUNCA | BOOL PathIsUNCA(LPCTSTR pszPath); | shlwapi.dll | Cette fonction permet de demander si le répertoire est un répertoire UNC (Universal Naming Convention) valide.
PathIsUNCServerA | BOOL PathIsUNCServerA( LPCTSTR pszPath); | shlwapi.dll | Cette fonction permet de demander si le répertoire est un répertoire UNC (Universal Naming Convention) valide pour un serveur.
PathIsURLA | BOOL PathIsURLA(LPCTSTR pszPath); | shlwapi.dll | Cette fonction permet de demander si le répertoire est un répertoire URL valide.
PathToRegion | HRGN PathToRegion(HDC hdc); | gdi32.dll | Cette fonction permet de créer un chemin vers une région sélectionné dans le périphérique de contexte spécifié.
PathUndecorateA | void PathUndecorateA(LPTSTR pszPath); | shlwapi.dll | Cette fonction permet d'illiminer les séquences à numéro d'un nom fichier.
PathUnmakeSystemFolderA | BOOL PathUnmakeSystemFolderA(LPTSTR pszPath); | shlwapi.dll | Cette fonction permet d'enlever l'attribut système d'un répertoire spécifié.
PeekConsoleInputA | BOOL PeekConsoleInputA(HANDLE hConsoleInput, PINPUT_RECORD pirBuffer, DWORD cInRecords, LPDWORD lpcRead); | kernel32.dll | Cette fonction permet d'effectuer la lecture des données du tampon d'entrée de la console spécifié sans retirer le contenu lue du tampon.
PeekConsoleInputW | BOOL PeekConsoleInputW(HANDLE hConsoleInput,PINPUT_RECORD pirBuffer,DWORD cInRecords,PDWORD lpcRead); | kernel32.dll | Cette fonction permet d'effectuer la lecture des données du tampon d'entrée de la console spécifié sans retirer le contenu lue du tampon avec des paramètres de chaine de caractères étendue (Unicode).
PeekMessageA | BOOL PeekMessageA(LPMSG lpMsg, HWND hWnd, UINT uMsgFilterMin, UINT uMsgFilterMax, UINT wRemoveMsg); | user32.dll | Cette fonction permet de poster un message dans une fenêtre de file d'attente de message et retourne sans attendre la correspondance à un processus de message.
PeekMessageW | BOOL PeekMessageW(LPMSG lpMsg, HWND hWnd, UINT uMsgFilterMin, UINT uMsgFilterMax, UINT wRemoveMsg); | user32.dll | Cette fonction permet de poster un message dans une fenêtre de file d'attente de message et retourne sans attendre la correspondance à un processus de message avec des paramètres de chaine de caractères étendue (Unicode).
PeekNamedPipe | BOOL PeekNamedPipe(HANDLE hPipe, LPVOID lpvBuffer, DWORD cbBuffer, LPDWORD lpcbRead, LPDWORD lpcbAvail, LPDWORD lpcbMessage); | kernel32.dll | Cette fonction permet de copier les données d'un tuyau nommé ou anonyme dans un tampon sans le retirer du tuyau.
Pie | BOOL Pie(HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, int nXRadial1, int nYRadial1, int nXRadial2, int nYRadial2); | gdi32.dll | Cette fonction permet de dessiner une pointe de tarte en effectuant un arc elliptique d'un centre et de deux points join par des lignes.
PlayEnhMetaFile | BOOL PlayEnhMetaFile(HDC hdc, HENHMETAFILE hemf, CONST RECT * lpRect); | gdi32.dll | Cette fonction permet d'afficher l'image entreposé dans le fichier méta de format étendue spécifié.
PlayEnhMetaFileRecord | BOOL PlayEnhMetaFileRecord(HDC hdc, LPHANDLETABLE lpHandletable, CONST ENHMETARECORD * lpEnhMetaRecord, UINT nHandles); | gdi32.dll | Cette fonction permet de jouer un enregistrement de fichier méta étendue par exécution de fonction GDI (Graphics Device Interface) identifié par l'enregistrement.
PlayMetaFile | BOOL PlayMetaFile(HDC hdc, HMETAFILE hmf); | gdi32.dll | Cette fonction permet de jouer le contenu d'un fichier méta de format Windows spécifié dans un périphérique spécifié.
PlayMetaFileRecord | BOOL PlayMetaFileRecord(HDC hdc, LPHANDLETABLE lpHandletable, LPMETARECORD lpMetaRecord, UINT nHandles); | gdi32.dll | Cette fonction permet de jouer un enregistrement d'un fichier méta de format Windows par exécution du GDI contenu dans l'enregistrement.
PlaySoundA | BOOL PlaySoundA(LPCSTR pszSound, HMODULE hmod, DWORD fdwSound); | winmm.dll | Cette fonction permet de faire jouer un fichier de son de format Wave (.WAV).
PlgBlt | BOOL PlgBlt(HDC hdcDest, CONST POINT * lpPoint, HDC hdcSrc, int nXSrc, int nYSrc, int nWidth, int nHeight, HBITMAP hbmMask, int xMask, int yMask); | gdi32.dll | Cette fonction permet d'effectuer un traitement de transfert de bloc de bits d'une donnée de bits de couleurs d'un rectangle spécifié dans une source de périphérique de contexte vers la destination de périphérique de contexte de parallélogramme spécifié.
PolyBezier | BOOL PolyBezier(HDC hdc, CONST POINT * lppt, DWORD cPoints); | gdi32.dll | Cette fonction permet de dessiner une ou plusieurs courbes de Bézier.
PolyBezierTo | BOOL PolyBezierTo(HDC hdc, CONST POINT * lppt, DWORD cCount); | gdi32.dll | Cette fonction permet de dessiner une ou plusieurs courbes de Bézier spécifié.
PolyDraw | BOOL PolyDraw(HDC hdc, CONST POINT * lppt, CONST BYTE * lpbTypes, int cCount); | gdi32.dll | Cette fonction permet de dessiner un ensemble de ligne de segments et de courbes de Bézier.
PolyPolygon | BOOL PolyPolygon(HDC hdc, CONST POINT *lpPoints, LPINT lpPolyCounts, int nCount); | gdi32.dll | Cette fonction permet de créer deux ou plusieurs polygone étant rempli par un mode de remplissage de polygone.
PolyTextOutA | BOOL PolyTextOutA(HDC hdc, CONST POLYTEXT * pptxt, int cStrings); | gdi32.dll | Cette fonction permet de dessiner une chaine de caractères en utilisant la police de caractères et les couleurs textes courante dans le périphérique de contexte spécifié.
PolyTextOutW | BOOL PolyTextOutW(HDC hdc, const POLYTEXTW* pptxt, INT cStrings); | gdi32.dll | Cette fonction permet de dessiner une chaine de caractères en utilisant la police de caractères et les couleurs textes courante dans le périphérique de contexte spécifié avec des paramètres de chaine de caractères étendues.
Polygon | BOOL Polygon(HDC hdc, CONST POINT *lpPoints, int nCount); | gdi32.dll | Cette fonction permet de dessiner un polygone de deux ou plusieurs points (sommets) connectés par des lignes.
Polyline | BOOL Polyline(HDC hdc, CONST POINT *lppt, int cPoints); | gdi32.dll | Cette fonction permet de dessiner un ensemble de segments de lignes, connecté par les points spécifiés.
PolylineTo | BOOL PolylineTo(HDC hdc, CONST POINT * lppt, DWORD cCount); | gdi32.dll | Cette fonction permet de dessiner une ou plusieurs lignes droite.
PostMessageA | BOOL PostMessageA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'envoyer un message à un objet du système d'exploitation Windows.
PostMessageW | BOOL PostMessageW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'envoyer un message à un objet du système d'exploitation Windows avec des paramètres de chaine de caractères étendues (Unicode).
PostQueuedCompletionStatus | BOOL PostQueuedCompletionStatus(HANDLE CompletionPort, DWORD dwNumberOfBytesTransferred, DWORD dwCompletionKey, LPOVERLAPPED lpOverlapped); | kernel32.dll | Cette fonction permet de poster une paquet d'achèvement d'entrée/sortie vers un port d'achèvement d'entrée/sortie.
PostQuitMessage | VOID PostQuitMessage(int nExitCode); | user32.dll | Cette fonction permet de poster un message à Windows afin d'indiquer que l'application reçoit une requête de terminaison d'exécution (quitte).
PostThreadMessageA | BOOL PostThreadMessageA(DWORD dwThreadId, UINT uMsg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet de poster un message dans la file d'attente du processus léger spécifié et retourne sans attendre que le processus léger traite le message.
PostThreadMessageW | BOOL PostThreadMessageW(DWORD dwThreadId, UINT uMsg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet de poster un message dans la file d'attente du processus léger spécifié et retourne sans attendre que le processus léger traite le message avec des paramètres de chaine de caractères étendues (Unicode).
PrepareTape | DWORD PrepareTape(HANDLE hDevice, DWORD dwOperation, BOOL bImmediate); | kernel32.dll | Cette fonction permet de préparer le ruban pour être accédé ou enlevé.
PrintDlgA | BOOL PrintDlgA(LPPRINTDLG lppd); | comdlg32.dll | Cette fonction permet d'afficher une boite de dialogue d'impression.
PrinterProperties | BOOL PrinterProperties(hwnd hwnd,HANDLE hPrinter); | winspool.drv | Cette fonction permet d'afficher une boite de dialogue de propriété de l'imprimante.
PrivilegeCheck | BOOL PrivilegeCheck( HANDLE hClientToken, PPRIVILEGE_SET pps, LPBOOL lpfResult ); | advapi32.dll | Cette fonction permet de tester le contexte de sécurité représenté par un accès de jeton spécifié contenu dans un prévilège spécifié.
PrivilegedServiceAuditAlarmA | BOOL PrivilegedServiceAuditAlarmA( LPCTSTR lpszSubsystem, LPCTSTR lpszService, HANDLE hClientToken, PPRIVILEGE_SET pps, BOOL fAccessGranted ); | advapi32.dll | Cette fonction permet de générer un message de journal de bord quand une opération de service de prévilège système s'effectue.
PrivilegedServiceAuditAlarmW | BOOL PrivilegedServiceAuditAlarmW(LPCWSTR SubsystemName, LPCWSTR ServiceName, HANDLE ClientToken, PPRIVILEGE_SET Privileges, BOOL AccessGranted ); | advapi32.dll | Cette fonction permet de générer un message de journal de bord quand une opération de service de prévilège système s'effectue avec des paramètres de chaine de caractères étendue (Unicode).
Process32First | BOOL WINAPI Process32First(HANDLE hSnapshot,LPPROCESSENTRY32 lppe); | kernel32.dll | Cette fonction permet de demander le premier processus provenant de la liste contenu dans une photo du système d'exploitation.
Process32Next | BOOL WINAPI Process32Next( HANDLE hSnapshot, LPPROCESSENTRY32 lppe); | kernel32.dll | Cette fonction permet de demander le processus suivant provenant de la liste contenu dans une photo du système d'exploitation.
PtInRect | BOOL PtInRect(CONST RECT *lprc, POINT pt); | user32.dll | Cette fonction permet de déterminer si le point spécifié est lié avec le rectangle spécifié.
PtInRegion | BOOL PtInRegion(HRGN hrgn, int X, int Y); | gdi32.dll | Cette fonction permet de demander si un point spécifié est dans une région spécifié.
PtVisible | BOOL PtVisible(HDC hdc, int X, int Y); | gdi32.dll | Cette fonction permet de demander si le point spécifié est dans la région clip d'un périphérique de contexte.
PulseEvent | BOOL PulseEvent(HANDLE hEvent); | kernel32.dll | Cette fonction permet de fournir une simple opération fixant l'état d'un objet d'événement spécifié et réinitialisant après qu'un délai d'attente de processus approprié s'est produit.
PurgeComm | BOOL PurgeComm(HANDLE hCommDev, DWORD fdwAction); | kernel32.dll | Cette fonction permet de débarrasser de tous les caractères d'un tampon de sortie ou d'entrée d'une ressource de communication spécifié.
PwdChangePasswordA | DWORD WINAPI PwdChangePasswordA(LPCSTR lpProvider, HWND hWnd, DWORD flags, LPCHANGEPWDINFOA info); | mpr.dll | Cette fonction permet de modifier le mot de passe de l'utilisateur ou d'un fournisseur du système d'exploitation.
QueryDosDeviceA | DWORD QueryDosDeviceA(LPCTSTR lpDeviceName, LPTSTR lpTargetPath, DWORD ucchMax); | kernel32.dll | Cette fonction permet à une application de demander des informations à propre d'un nom de périphérique DOS.
QueryDosDeviceW | DWORD QueryDosDeviceW(LPCWSTR lpDeviceName, LPCWSTR lpTargetPath, DWORD ucchMax); | kernel32.dll | Cette fonction permet à une application de demander des informations à propre d'un nom de périphérique DOS avec des paramètres de chaine de caractères étendue (Unicode).
QueryPerformanceCounter | BOOL QueryPerformanceCounter(LARGE_INTEGER *lpliPerformanceCount); | kernel32.dll | Cette fonction permet de demander la valeur actuel du compteur de haute performance de la machine.
QueryPerformanceFrequency | BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpliPerformanceFreq); | kernel32.dll | Cette fonction permet de demander la valeur de la fréquence du compteur de haute performance de la machine.
QueueUserAPC | DWORD QueueUserAPC(PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData); | kernel32.dll | Cette fonction permet d'ajouter une objet de procédure d'appel désychronisée (APC) en mode utilisateur dans une file d'attente APC d'un processus léger spécifié.
RaiseException | VOID RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD cArguments, CONST DWORD * lpArguments); | kernel32.dll | Cette fonction permet de déclencher une exception dans le processus léger appelant.
RasEnumEntriesA | DWORD RasEnumEntriesA( LPTSTR reserved, LPTSTR lpszPhonebook, LPRASENTRYNAME lprasentryname, LPDWORD lpcb, LPDWORD lpcEntries ); | rasapi32.dll | Cette fonction permet de demander la liste de tous les noms d'entrées contenu dans le carnet pour les connexions téléphoniques à distance.
ReadConsoleA | BOOL ReadConsoleA(HANDLE hConsoleInput, LPVOID lpvBuffer, DWORD cchToRead, LPDWORD lpcchRead, LPVOID lpvReserved); | kernel32.dll | Cette fonction permet d'effectuer la lecture d'un caractère en provenance de la console.
ReadConsoleW | BOOL ReadConsoleW(HANDLE hConsoleInput, LPVOID lpBuffer, DWORD nNumberOfCharsToRead, LPDWORD lpNumberOfCharsRead, LPVOID lpReserved); | kernel32.dll | Cette fonction permet d'effectuer la lecture d'un caractère en provenance de la console avec des paramètres de chaine de caractères étendue (Unicode).
ReadConsoleInputA | BOOL ReadConsoleInputA(HANDLE hConsoleInput, PINPUT_RECORD pirBuffer, DWORD cInRecords, LPDWORD lpcRead); | kernel32.dll | Cette fonction permet d'effectuer la lecture de données du tampon d'entrée d'une console et retire les données du tampon.
ReadConsoleInputW | BOOL ReadConsoleInputW(HANDLE hConsoleInput, PINPUT_RECORD pirBuffer, DWORD cInRecords, LPDWORD lpcRead); | kernel32.dll | Cette fonction permet d'effectuer la lecture de données du tampon d'entrée d'une console et retire les données du tampon avec des paramètres de chaine de caractères étendue (Unicode).
ReadConsoleOutputA | BOOL ReadConsoleOutputA(HANDLE hConsoleOutput, PCHAR_INFO pchiDestBuffer, COORD coordDestBufferSize, COORD coordDestBufferCoord, PSMALL_RECT psrctSourceRect); | kernel32.dll | Cette fonction permet d'effectuer la lecture de données de caractère et de couleur d'attribut dans un bloc rectangulaire d'une cellule de caractères d'un tampon de console d'écran et écrire le résultat de ses données dans un tampon spécifié.
ReadConsoleOutputW | BOOL ReadConsoleOutputW(HANDLE hConsoleOutput, PCHAR_INFO pchiDestBuffer, COORD coordDestBufferSize, COORD coordDestBufferCoord, PSMALL_RECT psrctSourceRect); | kernel32.dll | Cette fonction permet d'effectuer la lecture de données de caractère et de couleur d'attribut dans un bloc rectangulaire d'une cellule de caractères d'un tampon de console d'écran et écrire le résultat de ses données dans un tampon spécifié avec des paramètres de chaine de caractères étendue (Unicode).
ReadConsoleOutputAttribute | BOOL ReadConsoleOutputAttribute(HANDLE hConsoleOutput, LPWORD lpwAttribute, DWORD cReadCells, COORD coordReadCoord, LPDWORD lpcNumberRead); | kernel32.dll | Cette fonction permet de copier le nombre spécifié dans la couleur d'attribut d'arrière-plan ou de premier plan d'une cellule consécutive d'un tampon de console d'écran en débutant à la position spécifié.
ReadConsoleOutputCharacterA | BOOL ReadConsoleOutputCharacterA(HANDLE hConsoleOutput, LPTSTR lpReadBuffer, DWORD cchRead, COORD coordReadCoord, LPDWORD lpcNumberRead); | kernel32.dll | Cette fonction permet de copier un nombre de caractères d'une cellule consécutif d'un tampon de console écran en débutant à la position spécifié.
ReadConsoleOutputCharacterW | BOOL ReadConsoleOutputCharacterW(HANDLE hConsoleOutput, LPCWSTR lpReadBuffer, DWORD cchRead, COORD coordReadCoord, LPDWORD lpcNumberRead); | kernel32.dll | Cette fonction permet de copier un nombre de caractères d'une cellule consécutif d'un tampon de console écran en débutant à la position spécifié avec des paramètres de chaine de caractères étendue (Unicode).
ReadDirectoryChangesW | BOOL ReadDirectoryChangesW( HANDLE hDirectory, LPVOID lpBuffer, DWORD nBufferLength, BOOL bWatchSubtree, DWORD dwNotifyFilter, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine ); | kernel32.dll | Cette fonction permet de demander les informations descriptives de changement ayant eu lieu dans le répertoire spécifié.
ReadEventLogA | BOOL ReadEventLogA( HANDLE hEventLog, DWORD dwReadFlags, DWORD dwRecordOffset, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, DWORD *pnBytesRead, DWORD *pnMinNumberOfBytesNeeded ); | advapi32.dll | Cette fonction permet de lire un nombre d'entrées spécifié dans un journal de bord des événements.
ReadEventLogW | BOOL ReadEventLogW( HANDLE hEventLog, DWORD dwReadFlags, DWORD dwRecordOffset, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, DWORD* pnBytesRead, DWORD* pnMinNumberOfBytesNeeded ); | advapi32.dll | Cette fonction permet de lire un nombre d'entrées spécifié dans un journal de bord des événements avec des paramètres de chaine de caractères étendue (Unicode).
ReadFile | BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped); | kernel32.dll | Cette fonction permet d'effectuer la lecture d'un fichier, en débutant à la position spécifié par le pointeur de fichier.
ReadFileEx | BOOL ReadFileEx(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPOVERLAPPED lpOverlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); | kernel32.dll | Cette fonction permet d'effectuer la lecture de données d'un fichier désychronisé.
ReadProcessMemory | BOOL ReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, DWORD cbRead, LPDWORD lpNumberOfBytesRead); | kernel32.dll | Cette fonction permet d'effectuer la lecture de mémoire dans un processus spécifié.
RealizePalette | UINT RealizePalette(HDC hdc); | gdi32.dll | Cette fonction permet de cartographier l'entrée de palette de la palette logique courante de la palette système.
Rectangle | BOOL Rectangle(HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect); | gdi32.dll | Cette fonction permet d'afficher un rectangle.
RectInRegion | BOOL RectInRegion(HRGN hrgn, CONST RECT *lprc); | gdi32.dll | Cette fonction permet de demander si une partie du rectangle spécifié est dans une des limites d'une région spécifié.
RectVisible | BOOL RectVisible(HDC hdc, CONST RECT *lprc); | gdi32.dll | Cette fonction permet de demander n'importe quel partie d'un rectangle spécifié dans une région de clip d'un périphérique de contexte spécifié.
RedrawWindow | BOOL RedrawWindow(HWND hwnd, CONST RECT *lprcUpdate, HRGN hrgnUpdate, UINT afuRedraw); | user32.dll | Cette fonction permet de mettre à jour le rectangle ou la région spécifié de la région de la fenêtre client.
RegCloseKey | LONG RegCloseKey( HKEY hKey ); | advapi32.dll | Cette fonction permet de fermer le Handle de la clef de registre spécifié.
RegConnectRegistryA | LONG RegConnectRegistryA( LPTSTR lpszComputerName, HKEY hKey, PHKEY phkResult ); | advapi32.dll | Cette fonction permet d'établir une connexion avec un registre de Handle prédéfinie ou autre ordinateur.
RegConnectRegistryW | LSTATUS RegConnectRegistryW( LPCWSTR lpMachineName, HKEY hKey, PHKEY phkResult ); | advapi32.dll | Cette fonction permet d'établir une connexion avec un registre de Handle prédéfinie ou autre ordinateur avec des paramètres de chaine de caractères étendue (Unicode).
RegCreateKeyA | LONG RegCreateKeyA( HKEY hKey, LPCTSTR lpszSubKey, PHKEY phkResult ); | advapi32.dll | Cette fonction permet de créer la clef de registre spécifié. Si la clef existe dans les registres, la fonction l'ouvre.
RegCreateKeyW | LSTATUS RegCreateKeyW( HKEY hkey, LPCWSTR lpSubKey, PHKEY phkResult ); | advapi32.dll | Cette fonction permet de créer la clef de registre spécifié avec des paramètres de chaine de caractères étendue (Unicode). Si la clef existe dans les registres, la fonction l'ouvre.
RegCreateKeyExA | LONG RegCreateKeyExA( HKEY hKey, LPCTSTR lpszSubKey, DWORD dwReserved, LPTSTR lpszClass, DWORD fdwOptions, REGSAM samDesired, LPSECURITY_ATTRIBUTES lpSecurityAttributes, PHKEY phkResult, LPDWORD lpdwDisposition ); | advapi32.dll | Cette fonction permet de créer une clef de registre spécifié avec des systèmes de la famille NT. Si la clef existe dans les registres, la fonction l'ouvre.
RegCreateKeyExW | LSTATUS RegCreateKeyExW( HKEY hkey, LPCWSTR name, DWORD reserved, LPWSTR class, DWORD options, REGSAM access, SECURITY_ATTRIBUTES* sa, PHKEY retkey, LPDWORD dispos ); | advapi32.dll | Cette fonction permet de créer une clef de registre spécifié avec des systèmes de la famille NT avec des paramètres de chaine de caractères étendue (Unicode). Si la clef existe dans les registres, la fonction l'ouvre.
RegDeleteKeyA | LONG RegDeleteKeyA( HKEY hKey, LPCTSTR lpszSubKey ); | advapi32.dll | Cette fonction permet de supprimer la clef de registre spécifié.
RegDeleteKeyW | LSTATUS RegDeleteKeyW( HKEY hkey, LPCWSTR name ); | advapi32.dll | Cette fonction permet de supprimer la clef de registre spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegDeleteValueA | LONG RegDeleteValueA( HKEY hKey, LPTSTR lpszValue ); | advapi32.dll | Cette fonction permet de supprimer la valeur du nom spécifié dans la clef de la base de registre.
RegDeleteValueW | LSTATUS RegDeleteValueW( HKEY hkey, LPCWSTR name ); | advapi32.dll | Cette fonction permet de supprimer la valeur du nom spécifié dans la clef de la base de registre avec des paramètres de chaine de caractères étendue (Unicode).
RegEnumKeyA | LONG RegEnumKeyA( HKEY hKey, DWORD iSubKey, LPTSTR lpszName, DWORD cchName ); | advapi32.dll | Cette fonction permet d'énuméré les sous-clefs d'une clef de registre ouverte spécifié.
RegEnumKeyW | LSTATUS RegEnumKeyW( HKEY hkey, DWORD index, LPWSTR name, DWORD name_len ); | advapi32.dll | Cette fonction permet d'énuméré les sous-clefs d'une clef de registre ouverte spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegEnumKeyExA | LONG RegEnumKeyExA( HKEY hKey, DWORD iSubkey, LPTSTR lpszName, LPDWORD lpcchName, LPDWORD lpdwReserved, LPTSTR lpszClass, LPDWORD lpcchClass, PFILETIME lpftLastWrite ); | advapi32.dll | Cette fonction permet d'énuméré les sous-clefs d'une clef de registre ouverte spécifié avec des systèmes de la famille NT.
RegEnumKeyExW | LSTATUS RegEnumKeyExW( HKEY hkey, DWORD index, LPWSTR name, LPDWORD name_len, LPDWORD reserved, LPWSTR class, LPDWORD class_len, FILETIME* ft ); | advapi32.dll | Cette fonction permet d'énuméré les sous-clefs d'une clef de registre ouverte spécifié avec des systèmes de la famille NT avec des paramètres de chaine de caractères étendue (Unicode).
RegEnumValueA | LONG RegEnumValueA( HKEY hKey, DWORD iValue, LPTSTR lpszValue, LPDWORD lpcchValue, LPDWORD lpdwReserved, LPDWORD lpdwType, LPBYTE lpbData, LPDWORD lpcbData ); | advapi32.dll | Cette fonction permet d'énuméré les valeurs pour la clef de registre ouverte spécifié.
RegEnumValueW | LSTATUS RegEnumValueW( HKEY hkey, DWORD index, LPWSTR value, LPDWORD val_count, LPDWORD reserved, LPDWORD type, LPBYTE data, LPDWORD count ); | advapi32.dll | Cette fonction permet d'énuméré les valeurs pour la clef de registre ouverte spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegFlushKey | LONG RegFlushKey(HKEY hKey); | advapi32.dll | Cette fonction permet d'écrire tous les attributs de la clef de la base de registre ouverte spécifié.
RegGetKeySecurity | LSTATUS RegGetKeySecurity( HKEY hkey, SECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR pSecurityDescriptor, LPDWORD lpcbSecurityDescriptor ); | advapi32.dll | Cette fonction permet de copier le descripteur de sécurité de la clef de registre ouverte spécifié.
RegisterClassA | ATOM RegisterClassA(CONST WNDCLASS *lpwc); | user32.dll | Cette fonction permet d'effectuer l'enregistrement d'une classe de fenêtre subséquente aux appels des fonctions CreateWindow ou CreateWindowEx.
RegisterClassExA | ATOM RegisterClassExA(CONST WNDCLASSEX FAR *lpwcx); | user32.dll | Cette fonction permet d'effectuer l'enregistrement d'une classe de fenêtre subséquente aux appels des fonctions CreateWindow ou CreateWindowEx avec des options supplémentaires.
RegisterClassExW | ATOM RegisterClassExW(CONST WNDCLASSEXW FAR *lpwcx); | user32.dll | Cette fonction permet d'effectuer l'enregistrement d'une classe de fenêtre subséquente aux appels des fonctions CreateWindow ou CreateWindowEx avec des options supplémentaires et des paramètres de chaine de caractères étendues (Unicode).
RegisterClassW | ATOM RegisterClassW(CONST WNDCLASSW *lpwc); | user32.dll | Cette fonction permet d'effectuer l'enregistrement d'une classe de fenêtre subséquente aux appels des fonctions CreateWindow ou CreateWindowEx avec des paramètres de chaine de caractères étendues (Unicode).
RegisterClipboardFormatA | UINT RegisterClipboardFormatA(LPCTSTR lpszFormat); | user32.dll | Cette fonction permet d'enregistrer un nouveau format de presse-papier.
RegisterClipboardFormatW | UINT RegisterClipboardFormatW(LPCWSTR lpszFormat); | user32.dll | Cette fonction permet d'enregistrer un nouveau format de presse-papier avec des paramètres de chaine de cararactères étendues (Unicode).
RegisterDeviceNotificationA | HDEVNOTIFY RegisterDeviceNotificationA( HANDLE hRecipient, LPVOID NotificationFilter, DWORD Flags ); | user32.dll | Cette fonction permet d'enregistrer un périphérique ou un type de périphérique pour que la fenêtre reçoivent des avertissements de changements.
RegisterHotKey | BOOL RegisterHotKey(HWND hwnd, int idHotKey, UINT fuModifiers, UINT uVirtKey); | user32.dll | Cette fonction permet de définir l'état des touches de combinaison (Alt, Ctrl, Shift) pour le processus léger courant.
RegisterServiceProcess | DWORD RegisterServiceProcess(DWORD dwProcessId,DWORD dwType); | kernel32.dll | Cette fonction permet de cacher un programme de facon à ce qu'il ne soit pas afficher lorsqu'on appuis sur Ctrl+Alt+Delete pour voir la liste des programmes dans le Gestionnaire de tâche.
RegisterWindowMessageA | UINT RegisterWindowMessageA(LPCTSTR lpsz); | user32.dll | Cette fonction permet de définir une nouvelle fenêtre de message garantissant qu'elle est unique au système.
RegisterWindowMessageW | UINT RegisterWindowMessageW(LPCWSTR lpsz); | user32.dll | Cette fonction permet de définir une nouvelle fenêtre de message garantissant qu'elle est unique au système avec des paramètres de chaine de caractères étendues (Unicode).
RegLoadKeyA | LONG RegLoadKeyA( HKEY hKey, LPCTSTR lpszSubKey, LPCTSTR lpszFile ); | advapi32.dll | Cette fonction permet de créer une sous-clef dans «HKEY_USER» ou «HKEY_LOCAL_MACHINE» et d'entreposer les informations d'enregistrement dans le fichier spécifié d'une sous-clef.
RegLoadKeyW | LSTATUS RegLoadKeyW( HKEY hkey, LPCWSTR subkey, LPCWSTR filename ); | advapi32.dll | Cette fonction permet de créer une sous-clef dans «HKEY_USER» ou «HKEY_LOCAL_MACHINE» et d'entreposer les informations d'enregistrement dans le fichier spécifié d'une sous-clef avec des paramètres de chaine de caractères étendue (Unicode).
RegNotifyChangeKeyValue | LONG RegNotifyChangeKeyValue( HKEY hKey, BOOL fWatchSubTree, DWORD fdwNotifyFilter, HANDLE hEvent, BOOL fAsync ); | advapi32.dll | Cette fonction permet d'indiquer quand une clef de registre, ou élément rattaché à celle-ci, a changé.
RegOpenKeyA | LONG RegOpenKeyA( HKEY hKey, LPCTSTR lpszSubKey, PHKEY phkResult ); | advapi32.dll | Cette fonction permet d'ouvrir une clef spécifié dans une base de registre.
RegOpenKeyW | LSTATUS RegOpenKeyW( HKEY hkey, LPCWSTR name, PHKEY retkey ); | advapi32.dll | Cette fonction permet d'ouvrir une clef spécifié dans une base de registre avec des paramètres de chaine de caractères étendue (Unicode).
RegOpenKeyExA | LONG RegOpenKeyExA( HKEY hKey, LPCTSTR lpszSubKey, DWORD dwReserved, REGSAM samDesired, PHKEY phkResult ); | advapi32.dll | Cette fonction permet d'ouvrir une clef spécifié dans une base de registre.
RegOpenKeyExW | LSTATUS RegOpenKeyExW( HKEY hkey, LPCWSTR name, DWORD reserved, REGSAM access, PHKEY retkey ); | advapi32.dll | Cette fonction permet d'ouvrir une clef spécifié dans une base de registre avec des paramètres de chaine de caractères étendue (Unicode).
RegQueryInfoKeyA | LONG RegQueryInfoKeyA( HKEY hKey, LPTSTR lpszClass, LPDWORD lpcchClass, LPDWORD lpdwReserved, LPDWORD lpcSubKeys, LPDWORD lpcchMaxSubkey, LPDWORD lpcchMaxClass, LPDWORD lpcValues, LPDWORD lpcchMaxValueName, LPDWORD lpcbMaxValueData, LPDWORD lpcbSecurityDescriptor, PFILETIME lpftLastWriteTime ); | advapi32.dll | Cette fonction permet de demander les informations d'une clef de registre spécifié.
RegQueryInfoKeyW | LSTATUS RegQueryInfoKeyW( HKEY hkey, LPWSTR class, LPDWORD class_len, LPDWORD reserved, LPDWORD subkeys, LPDWORD max_subkey, LPDWORD max_class, LPDWORD values, LPDWORD max_value, LPDWORD max_data, LPDWORD security, FILETIME* modif ); | advapi32.dll | Cette fonction permet de demander les informations d'une clef de registre spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegQueryMultipleValuesA | LONG RegQueryMultipleValuesA( HKEY hKey, PVALENT val_list, DWORD num_vals, LPTSTR lpValueBuf, LPDWORD ldwTotsize ); | advapi32.dll | Cette fonction permet de demander le type et la donnée pour une liste de noms de valeur associé avec une clef de registre ouverte.
RegQueryMultipleValuesW | LSTATUS RegQueryMultipleValuesW( HKEY hkey, PVALENTW val_list, DWORD num_vals, LPWSTR lpValueBuf, LPDWORD ldwTotsize ); | advapi32.dll | Cette fonction permet de demander le type et la donnée pour une liste de noms de valeur associé avec une clef de registre ouverte avec des paramètres de chaine de caractères étendue (Unicode).
RegQueryValueA | LONG RegQueryValueA( HKEY hKey, LPCTSTR lpszSubKey, LPTSTR lpszValue, PLONG pcbValue ); | advapi32.dll | Cette fonction permet de demander la valeur associé avec une valeur non-nommé pour la clef du registre spécifié.
RegQueryValueW | LSTATUS RegQueryValueW( HKEY hkey, LPCWSTR name, LPWSTR data, LPLONG count ); | advapi32.dll | Cette fonction permet de demander la valeur associé avec une valeur non-nommé pour la clef du registre spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegQueryValueExA | LONG RegQueryValueExA( HKEY hKey, LPTSTR lpszValueName, LPDWORD lpdwReserved, LPDWORD lpdwType, LPBYTE lpbData, LPDWORD lpcbData ); | advapi32.dll | Cette fonction permet de demander la valeur associé avec une valeur non-nommé pour la clef du registre spécifié avec des systèmes de la famille NT.
RegQueryValueExW | LSTATUS RegQueryValueExW ( HKEY hkey, LPCWSTR name, LPDWORD reserved, LPDWORD type, LPBYTE data, LPDWORD count ); | advapi32.dll | Cette fonction permet de demander la valeur associé avec une valeur non-nommé pour la clef du registre spécifié avec des systèmes de la famille NT avec des paramètres de chaine de caractères étendue (Unicode).
RegReplaceKeyA | LONG RegReplaceKeyA( HKEY hKey, LPCTSTR lpSubKey, LPCTSTR lpNewFile, LPCTSTR lpOldFile ); | advapi32.dll | Cette fonction permet de remplacer un fichier de copie de sauvegarde d'un clef et tous les sous-clef d'une autre fichier.
RegReplaceKeyW | LSTATUS RegReplaceKeyW( HKEY hkey, LPCWSTR lpSubKey, LPCWSTR lpNewFile, LPCWSTR lpOldFile ); | advapi32.dll | Cette fonction permet de remplacer un fichier de copie de sauvegarde d'un clef et tous les sous-clef d'une autre fichier avec des paramètres de chaine de caractères étendue (Unicode).
RegRestoreKeyA | LONG RegRestoreKeyA( HKEY hKey, LPCTSTR lpszFile, DWORD fdw ); | advapi32.dll | Cette fonction permet de lire des informations registre dans un fichier spécifié et de copier la clef spécifié.
RegRestoreKeyW | LSTATUS RegRestoreKeyW( HKEY hkey, LPCWSTR lpFile, DWORD dwFlags ); | advapi32.dll | Cette fonction permet de lire des informations registre dans un fichier spécifié et de copier la clef spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegSaveKeyA | LONG RegSaveKeyA( HKEY hKey, LPCTSTR lpszFile, LPSECURITY_ATTRIBUTES lpsa ); | advapi32.dll | Cette fonction permet de sauvegarder tous les clefs, sous-clefs et les valeurs spécifié dans un nouveau fichier.
RegSaveKeyW | LSTATUS RegSaveKeyW ( HKEY hkey, LPCWSTR file, LPSECURITY_ATTRIBUTES sa ); | advapi32.dll | Cette fonction permet de sauvegarder tous les clefs, sous-clefs et les valeurs spécifié dans un nouveau fichier avec des paramètres de chaine de caractères étendue (Unicode).
RegSetKeySecurity | LONG RegSetKeySecurity( HKEY hKey, SECURITY_INFORMATION si, PSECURITY_DESCRIPTOR psd ); | advapi32.dll | Cette fonction permet fixer la sécurité d'une clef de registre ouvert.
RegSetValueA | LONG RegSetValueA( HKEY hKey, LPCTSTR lpSubKey, DWORD dwType, LPCTSTR lpData, DWORD cbData ); | advapi32.dll | Cette fonction permet d'associer une valeur avec une clef spécifié.
RegSetValueW | LSTATUS RegSetValueW ( HKEY hkey, LPCWSTR name, DWORD type, LPCWSTR data, DWORD count ); | advapi32.dll | Cette fonction permet d'associer une valeur avec une clef spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegSetValueExA | LONG RegSetValueExA( HKEY hKey, LPCTSTR lpValueName, DWORD Reserved, DWORD dwType, CONST BYTE * lpData, DWORD cbData ); | advapi32.dll | Cette fonction permet d'entreposer les données d'un champs de valeur d'une clef de registre ouvert.
RegSetValueExW | LSTATUS RegSetValueExW( HKEY hkey, LPCWSTR name, DWORD reserved, DWORD type, CONST BYTE* data, DWORD count ); | advapi32.dll | Cette fonction permet d'entreposer les données d'un champs de valeur d'une clef de registre ouvert avec des paramètres de chaine de caractères étendue (Unicode).
RegUnLoadKeyA | LONG RegUnLoadKeyA( HKEY hKey, LPCTSTR lpszSubKey ); | advapi32.dll | Cette fonction permet de décharger la clef et sous-clefs de registre spécifié
RegUnLoadKeyW | LSTATUS RegUnLoadKeyW( HKEY hkey, LPCWSTR lpSubKey ); | advapi32.dll | Cette fonction permet de décharger la clef et sous-clefs de registre spécifié avec des paramètres de chaine de caractères étendue (Unicode).
RegisterEventSourceA | HANDLE RegisterEventSourceA( LPCTSTR lpszUNCServer, LPCTSTR lpszSource ); | advapi32.dll | Cette fonction permet de retourner l'identificateur Handle utilisé avec la fonction d'événement «ReportEvent» du journal de bord.
RegisterEventSourceW | HANDLE RegisterEventSourceW( LPCWSTR lpUNCServerName, LPCWSTR lpSourceName ); | advapi32.dll | Cette fonction permet de retourner l'identificateur Handle utilisé avec la fonction d'événement «ReportEvent» du journal de bord avec des paramètres de chaine de caractères étendue (Unicode).
ReleaseCapture | BOOL ReleaseCapture(VOID); | user32.dll | Cette fonction permet de relâcher la capture de souris et de restituer l'entrée de traitement normal.
ReleaseDC | int ReleaseDC(HWND hwnd, HDC hdc); | user32.dll | Cette fonction permet de relâcher un périphérique de contexte, de libérer son utilisation pour les autres applications.
ReleaseMutex | BOOL ReleaseMutex(HANDLE hMutex); | kernel32.dll | Cette fonction permet de relâcher la propriété de l'objet Mutex spécifié.
ReleaseSemaphore | BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG cReleaseCount, LPLONG lplPreviousCount); | kernel32.dll | Cette fonction permet d'augmenter le compteur de l'objet de sémaphore spécifié par une quantité spécifié.
RemoveDirectoryA | BOOL RemoveDirectoryA(LPCTSTR lpszDir); | kernel32.dll | Cette fonction permet de supprimer un répertoire.
RemoveDirectoryW | BOOL RemoveDirectoryW(LPCWSTR lpszDir); | kernel32.dll | Cette fonction permet de supprimer un répertoire avec les paramètres de chaine de caractères étendues (Unicode).
RemoveDirectoryTransactedA | BOOL RemoveDirectoryTransactedA(LPCTSTR lpPathName, HANDLE hTransaction); | kernel32.dll | Cette fonction permet de supprimer un répertoire d'une opération de transaction.
RemoveDirectoryTransactedW | BOOL RemoveDirectoryTransactedW(LPCTWSTR lpPathName, HANDLE hTransaction); | kernel32.dll | Cette fonction permet de supprimer un répertoire d'une opération de transaction avec des paramètres de chaine de caractères étendue (Unicode).
RemoveFontResourceA | BOOL RemoveFontResourceA(LPCTSTR lpFileName); | gdi32.dll | Cette fonction permet d'enlever un ajout de ressource de police de caractères d'un fichier spécifié ou d'une table de police de caractères de Windows.
RemoveFontResourceW | BOOL RemoveFontResourceW(LPCWSTR lpFileName); | gdi32.dll | Cette fonction permet d'enlever un ajout de ressource de police de caractères d'un fichier spécifié ou d'une table de police de caractères de Windowsavec des paramètres de chaine de caractères étendues (Unicode).
RemoveMenu | BOOL RemoveMenu(HMENU hmenu, UINT uItem, UINT fuFlags); | user32.dll | Cette fonction permet de supprimer un item de menu déroulant.
RemovePropA | HANDLE RemovePropA(HWND hwnd, LPCTSTR lpsz); | user32.dll | Cette fonction permet d'enlever une entrée de la liste de propriété de la fenêtre spécifié.
RemovePropW | HANDLE RemovePropW(HWND hwnd, LPCWSTR lpsz); | user32.dll | Cette fonction permet d'enlever une entrée de la liste de propriété de la fenêtre spécifié avec des paramètres de chaine de caractères étendues (Unicode).
ReplyMessage | BOOL ReplyMessage(LRESULT lResult); | user32.dll | Cette fonction permet de répondre à un message envoyé par la fonction SendMessage sans rendre le contrôle à la fonction ayant appelé SendMessage.
ReportEventA | BOOL ReportEventA( HANDLE hEventSource, WORD fwEventType, WORD fwCategory, DWORD IDEvent, PSID pUserSid, WORD cStrings, DWORD cbData, LPCTSTR *plpszStrings, LPVOID lpvData ); | advapi32.dll | Cette fonction permet d'écrire une entrée à la fin du journal de bord des événements spécifié.
ReportEventW | BOOL ReportEventW( HANDLE hEventLog, WORD wType, WORD wCategory, DWORD dwEventID, PSID lpUserSid, WORD wNumStrings, DWORD dwDataSize, LPCWSTR* lpStrings, LPVOID lpRawData ); | advapi32.dll | Cette fonction permet d'écrire une entrée à la fin du journal de bord des événements spécifié avec des paramètres de chaine de caractères étendue (Unicode).
ResetDCA | HDC ResetDCA(HDC hdc, CONST DEVMODE * lpInitData); | gdi32.dll | Cette fonction permet de mettre à jour le périphérique de contexte, en se basant sur les informations de la structure spécifié.
ResetDCW | HDC ResetDCW(HDC hdc,const DEVMODEW * lpInitData); | gdi32.dll | Cette fonction permet de mettre à jour le périphérique de contexte, en se basant sur les informations de la structure spécifié avec des paramètres de chaine de caractères étendues (Unicode).
ResetEvent | BOOL ResetEvent(HANDLE hEvent); | kernel32.dll | Cette fonction permet de fixer l'état de l'objet d'événement spécifié dans un sans signal.
ResizePalette | BOOL ResizePalette(HPALETTE hpal, UINT nEntries); | gdi32.dll | Cette fonction permet de changer la taille de la palette logique spécifié.
RestoreDC | BOOL ResizePalette(HPALETTE hpal, UINT nEntries); | gdi32.dll | Cette fonction permet de restaurer un périphérique de contexte à l'état précédent.
RestoreMyDocsFolder | long RestoreMyDocsFolder(); | mydocs.dll | Cette fonction permet de recréer l'icône de «Mes Documents» sur le bureau du système d'exploitation Windows.
ResumeThread | DWORD ResumeThread(HANDLE hThread); | kernel32.dll | Cette fonction permet de décrémenter le compteur de processus léger suspendu.
ReuseDDElParam | LONG ReuseDDElParam(LONG lParam, UINT uMsgIn, UINT uMsgOut, UINT uLow, UINT uHigh); | user32.dll | Cette fonction permet d'allouer une application vers la réutilisation de paramètre DDE compacté spécifié et en réallouant un nouveau paquet spécifié.
RevertToSelf | BOOL RevertToSelf(VOID); | advapi32.dll | Cette fonction permet de terminé la personnification d'une application client.
RtlFillMemory | VOID RtlFillMemory(VOID* Destination, SIZE_T Length, BYTE Fill); | kernel32.dll | Cette fonction permet de remplir une zone de mémoire avec une valeur spécifié.
RtlMoveMemory | VOID RtlMoveMemory(VOID* Destination, CONST VOID* Source,SIZE_T Length); | kernel32.dll | Cette fonction permet de copier un bloc de mémoire vers un autre emplacement mémoire.
RoundRect | BOOL RoundRect(HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, int nWidth, int nHeight); | gdi32.dll | Cette fonction permet de dessiner un rectangle avec les coins arrondies, en utilisant le crayon courant.
SaveDC | int SaveDC(HDC hdc); | gdi32.dll | Cette fonction permet de sauvegarder l'état courant du périphérique de contexte courant en copiant les informations d'état (clip de région, sélection d'objets, et mode de cartographie) dans la pile de contexte.
ScaleViewportExtEx | BOOL ScaleViewportExtEx(HDC hdc, int Xnum, int Xdenom, int Ynum, int Ydenom, LPSIZE lpSize); | gdi32.dll | Cette fonction permet de modifier les extensions du point de vue relative avec des valeurs courante.
ScaleWindowExtEx | BOOL ScaleWindowExtEx(HDC hdc, int Xnum, int Xdenom, int Ynum, int Ydenom, LPSIZE lpSize); | gdi32.dll | Cette fonction permet de modifier les extensions de la fenêtre de périphérique de contexte en utilisant le ratio formé par le multiplicateur et le diviseur spécifié.
ScreenToClient | BOOL ScreenToClient(HWND hwnd, LPPOINT lpPoint); | user32.dll | Cette fonction permet de convertir les coordonnées d'écrans d'un point spécifié de l'écran à des coordonnées clients.
ScrollConsoleScreenBufferA | BOOL ScrollConsoleScreenBufferA(HANDLE hConsoleOutput, PSMALL_RECT psrctSourceRect, PSMALL_RECT psrctClipRect, COORD coordDestOrigin, PCHAR_INFO pchiFill); | kernel32.dll | Cette fonction permet de déplacer un bloc de données dans un tampon d'écran.
ScrollConsoleScreenBufferW | BOOL ScrollConsoleScreenBufferW(HANDLE hConsoleOutput, PSMALL_RECT psrctSourceRect, PSMALL_RECT psrctClipRect, COORD coordDestOrigin, PCHAR_INFO pchiFill); | kernel32.dll | Cette fonction permet de déplacer un bloc de données dans un tampon d'écran avec des paramètres de chaine de caractères étendues (Unicode).
ScrollDC | BOOL ScrollDC(HDC hdc, int dx, int dy, CONST RECT *lprcScroll, CONST RECT *lprcClip, HRGN hrgnUpdate, LPRECT lprcUpdate); | user32.dll | Cette fonction permet de faire défiler un rectangle d'un nombre de bits horizontal et vertical.
ScrollWindow | BOOL ScrollWindow(HWND hwnd, int dx, int dy, CONST RECT * lprcScroll, CONST RECT * lprcClip); | user32.dll | Cette fonction permet d'effectuer un défilement du contenu d'une région de fenêtre client.
ScrollWindowEx | int ScrollWindowEx(HWND hwnd, int dx, int dy, CONST RECT *lprcScroll, CONST RECT *lprcClip, HRGN hrgnUpdate, LPRECT lprcUpdate, UINT fuScroll); | user32.dll | Cette fonction permet d'effectuer un défilement du contenu d'une région de fenêtre client avec des options supplémentaires.
SearchPathA | DWORD SearchPathA(LPCTSTR lpszPath, LPCTSTR lpszFile, LPCTSTR lpszExtension, DWORD cchReturnBuffer, LPTSTR lpszReturnBuffer, LPTSTR *plpszFilePart); | kernel32.dll | Cette fonction permet d'effectuer une recherche dans le fichier spécifié.
SearchPathW | DWORD SearchPathW(LPCWSTR path, LPCWSTR name, LPCWSTR ext, DWORD buflen, LPWSTR buffer, LPWSTR* lastpart); | kernel32.dll | Cette fonction permet d'effectuer une recherche dans le fichier spécifié avec des paramètres de chaine de caractères étendues (Unicode).
SearchTreeForFile | BOOL SearchTreeForFile(PCTSTR RootPath, PCTSTR InputPathName,PTSTR OutputPathBuffer); | imagehlp.dll | Cette fonction permet d'effectuer la recherche d'un fichier dans un arbre de répertoire.
SelectClipPath | BOOL SelectClipPath(HDC hdc, int iMode); | gdi32.dll | Cette fonction permet de sélectionner le chemin courant dans la région de capture d'un périphérique de contexte en combinant la nouvelle région avec n'importe quel région de capture existante par utilisation de mode spécifié.
SelectClipRgn | int SelectClipRgn(HDC hdc, HRGN hrgn); | gdi32.dll | Cette fonction permet de sélectionner une région spécifié et la capture de région courant pour un périphérique de contexte courant.
SelectObject | HGDIOBJ SelectObject(HDC hdc, HGDIOBJ hgdiobj); | gdi32.dll | Cette fonction permet de sélectionner un objet dans le périphérique de contexte spécifié.
SelectPalette | HPALETTE SelectPalette(HDC hdc, HPALETTE hpal, BOOL bForceBackground); | gdi32.dll | Cette fonction permet de sélectionner la palette logique dans le périphérique de contexte donnée.
SendDlgItemMessageA | LONG SendDlgItemMessageA(HWND hwndDlg, int idControl, UINT uMsg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'envoyer un message à un contrôle de boite de dialogue.
SendDlgItemMessageW | LONG SendDlgItemMessageW(HWND hwndDlg, int idControl, UINT uMsg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'envoyer un message à un contrôle de boite de dialogue avec des paramètres de chaine de caractères étendues (Unidcode).
SendMessageA | LRESULT SendMessageA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message à une fenêtre où à un objet d'une fenêtre.
SendMessageCallbackA | BOOL SendMessageCallbackA(HWND hwnd, UINT uMsg, WPARAM wParam, PARAM lParam, SENDASYNCPROC lpResultCallBack, DWORD dwData); | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message spécifié vers une ou des fenêtres.
SendMessageCallbackW | BOOL SendMessageCallbackW(HWND hwnd, UINT uMsg, WPARAM wParam, PARAM lParam, SENDASYNCPROC lpResultCallBack, DWORD dwData); | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message spécifié vers une ou des fenêtres avec des paramètres de chaine de caractères étendues (Unicode).
SendMessageTimeoutA | LRESULT SendMessageTimeoutA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT fuFlags, UINT uTimeout, LPDWORD lpdwResult); | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message avec un dépassement de délai spécifié vers une ou des fenêtres.
SendMessageTimeoutW | LRESULT SendMessageTimeoutW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT fuFlags, UINT uTimeout, LPDWORD lpdwResult); | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message avec un dépassement de délai spécifié vers une ou des fenêtres avec des paramètres de chaine de caractères étendues (Unicode).
SendMessageW | LRESULT SendMessageW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message à une fenêtre où à un objet d'une fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
SendNotifyMessageA | BOOL SendNotifyMessageA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message d'avertissement spécifié vers une fenêtre.
SendNotifyMessageW | BOOL SendNotifyMessageW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); | user32.dll | Cette fonction permet d'effectuer l'envoi d'un message d'avertissement spécifié vers une fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
SetAbortProc | int SetAbortProc(HDC hdc, ABORTPROC lpAbortProc); | gdi32.dll | Cette fonction permet de fixer la procédure d'application définit par le programmeur pour alloué un travail d'impression afin d'être annuler durant sa tamporisation dans le tampon d'impression.
SetAclInformation | BOOL SetAclInformation( PACL pAcl, LPVOID lpvAclInfo, DWORD cbAclInfo, ACL_INFORMATION_CLASS aclic ); | advapi32.dll | Cette fonction permet de fixer les informations à propos de la liste de contrôle d'accès (ACL).
SetActiveWindow | HWND SetActiveWindow(HWND hwnd); | user32.dll | Cette fonction permet de rendre le plus haut niveau d'une fenêtre spécifié en une fenêtre active.
SetArcDirection | int SetArcDirection(HDC hdc, int ArcDirection); | gdi32.dll | Cette fonction permet de fixer la direction du dessinage pour être utilisé dans les fonctions d'arc et de rectangle.
SetBitmapBits | LONG SetBitmapBits(HBITMAP hbmp, DWORD cBytes, CONST VOID *lpBits); | gdi32.dll | Cette fonction permet de fixer l'ensemble de bits d'un Bitmap spécifié avec les valeurs de bits spécifié.
SetBitmapDimensionEx | BOOL SetBitmapDimensionEx(HBITMAP hBitmap, int nWidth, int nHeight, LPSIZE lpSize); | gdi32.dll | Cette fonction permet de fixer la taille souhaité d'un Bitmap, dans une unité de mesure de 0,01 millimètre.
SetBkColor | COLORREF SetBkColor(HDC hdc, COLORREF crColor); | gdi32.dll | Cette fonction permet de fixer la couleur de l'arrière-plan de la couleur spécifié.
SetBkMode | int SetBkMode(HDC hdc, int iBkMode); | gdi32.dll | Cette fonction permet de fixer le mode d'arrière-plan spécifié.
SetBoundsRect | UINT SetBoundsRect(HDC hdc, CONST RECT *lprcBounds, UINT flags); | gdi32.dll | Cette fonction permet de contrôler l'accumulation d'informations de limite rectangulaire d'un périphérique de contexte.
SetBrushOrgEx | BOOL SetBrushOrgEx(HDC hdc, int nXOrg, int nYOrg, LPPOINT lppt); | gdi32.dll | Cette fonction permet de fixer l'origine de la brosse GDI étant associé avec la prochaine brosse d'une application sélectionné dans le périphérique de contexte spécifié.
SetCapture | HWND SetCapture(HWND hwnd); | user32.dll | Cette fonction permet de fixer la capture de la souris dans la fenêtre spécifié.
SetCaretBlinkTime | BOOL SetCaretBlinkTime(UINT wMSeconds); | user32.dll | Cette fonction permet de fixer le taux de clignotement du caret.
SetCaretPos | BOOL SetCaretPos(int nX, int nY); | user32.dll | Cette fonction permet de fixer la position d'un caret.
SetClassLongA | DWORD SetClassLongA(HWND hwnd, int nIndex, LONG lNewVal); | user32.dll | Cette fonction permet de fixer des valeurs d'informations en 32 bits sur une classe.
SetClassLongW | DWORD SetClassLongW(HWND hwnd, int nIndex, LONG lNewVal); | user32.dll | Cette fonction permet de fixer des valeurs d'informations en 32 bits sur une classe avec des paramètres de chaine de caractères étendues (Unicode).
SetClassWord | WORD SetClassWord(HWND hwnd, int nIndex, WORD wNewWord); | user32.dll | Cette fonction permet de fixer la valeur d'un mot dans le déplacement spécifié de la classe de mémoire supplémentaire de la classe de fenêtre à laquelle la fenêtre appartient.
SetClipboardData | HANDLE SetClipboardData(UINT uFormat, HANDLE hData); | user32.dll | Cette fonction permet de fixer des données dans le presse-papier.
SetClipboardViewer | HWND SetClipboardViewer(HWND hwnd); | user32.dll | Cette fonction permet d'ajouter une fenêtre donnée à la chaine de fenêtre ayant un avertissement si le contenu du presse-papier a changé.
SetColorAdjustment | BOOL SetColorAdjustment(HDC hdc, CONST COLORADJUSTMENT * lpca); | gdi32.dll | Cette fonction permet de fixer les valeurs de couleurs d'ajustements pour un périphérique de contexte utilisant les valeurs spécifiés.
SetColorSpace | BOOL SetColorSpace(HDC hdc, HCOLORSPACE hColorSpace); | gdi32.dll | Cette fonction permet de fixer l'espace de couleur du périphérique de contexte spécifié en définissant la fin des points d'un espace RVB (RGB) logique dans l'espace CIE XYZ.
SetCommBreak | BOOL SetCommBreak(HANDLE hCommDev); | kernel32.dll | Cette fonction permet de suspendre la transmission de caractères pour un périphérique de communications spécifié et place la ligne de transmission dans un état d'arrêt jusqu'à ce que la fonction ClearCommBreak soit appelée.
SetCommConfig | BOOL SetCommConfig(HANDLE hCommDev, LPBYTE lpCC, DWORD dwSize); | kernel32.dll | Cette fonction permet de fixer la configuration courante dans un périphérique de communication.
SetCommMask | BOOL SetCommMask(HANDLE hCommDev, DWORD fdwEvtMask); | kernel32.dll | Cette fonction permet de spécifier un ensemble d'événement être surveillé par un périphérique de communication.
SetCommState | BOOL SetCommState(HANDLE hCommDev, LPDCB lpdcb); | kernel32.dll | Cette fonction permet de configurer un périphérique de communication en accord avec bloc de contrôle de périphérique (DCB) spécifié.
SetCommTimeouts | BOOL SetCommTimeouts(HANDLE hCommDev, LPCOMMTIMEOUTS lpctmo); | kernel32.dll | Cette fonction permet de fixer les paramètres de dépassement de délai pour tous les opérations de lecture ou d'écriture dans un périphérique de communication spécifié.
SetComputerNameA | BOOL SetComputerNameA(LPCTSTR lpszName); | kernel32.dll | Cette fonction permet de fixer le nom de l'ordinateur pour le système d'exploitation Windows.
SetComputerNameW | BOOL SetComputerNameW(LPCWSTR lpComputerName); | kernel32.dll | Cette fonction permet de fixer le nom de l'ordinateur pour le système d'exploitation Windows avec des paramètres de chaine de caractères étendues (Unicode).
SetConsoleActiveScreenBuffer | BOOL SetConsoleActiveScreenBuffer(HANDLE hConsoleOutput); | kernel32.dll | Cette fonction permet de fixer le tampon d'écran spécifié devant être actuellement affiché dans le tampon de la console d'écran.
SetConsoleCP | BOOL SetConsoleCP(UINT IDCodePage); | kernel32.dll | Cette fonction permet de fixer l'entrée de code de page utilisé par la console associé avec le processus appelant.
SetConsoleCtrlHandler | BOOL SetConsoleCtrlHandler(PHANDLER_ROUTINE pHandlerRoutine, BOOL fAdd); | kernel32.dll | Cette fonction permet d'ajouter un d'enlever une définition d'application de la fonction HandlerRoutine de la liste des fonctions de gestionnaire pour un processus appelant.
SetConsoleCursorInfo | BOOL SetConsoleCursorInfo(HANDLE hConsoleOutput, PCONSOLE_CURSOR_INFO pcci); | kernel32.dll | Cette fonction permet de fixer la taille et la visibilité du curseur dans le tampon de la console d'écran spécifié.
SetConsoleCursorPosition | BOOL SetConsoleCursorPosition( HANDLE hConsoleOutput, COORD coordCursor ); | kernel32.dll | Cette fonction permet de fixer la position du curseur pour l'affichage sur la console de texte.
SetConsoleMode | BOOL SetConsoleMode(HANDLE hConsole, DWORD fdwMode); | kernel32.dll | Cette fonction permet de fixer le mode d'entrée du tampon d'entrée de la console ou du mode de sortie d'un tampon de console d'écran.
SetConsoleOutputCP | BOOL SetConsoleOutputCP(UINT IDCodePage); | kernel32.dll | Cette fonction permet de fixer le code de page de sortie utilisé par la console associé avec le processus appelant.
SetConsoleScreenBufferSize | BOOL SetConsoleScreenBufferSize(HANDLE hConsoleOutput, COORD coordSize); | kernel32.dll | Cette fonction permet de changer la taille d'un tampon de console d'écran spécifié.
SetConsoleTextAttribute | BOOL SetConsoleTextAttribute(HANDLE hConsoleOutput, WORD wAttr); | kernel32.dll | Cette fonction permet de fixer les attributs (couleur de fond et d'écriture) pour l'affichage sur la console de texte. C'est l'équivalent d'un octet d'attribut dans un écran de texte matériel.
SetConsoleTitleA | BOOL SetConsoleTitleA(LPTSTR lpszTitle); | kernel32.dll | Cette fonction permet de fixer le titre de la barre de titre de la fenêtre d'application.
SetConsoleTitleW | BOOL SetConsoleTitleW(LPCWSTR title); | kernel32.dll | Cette fonction permet de fixer le titre de la barre de titre de la fenêtre d'application avec des paramètres de chaine de caractères étendues (Unicode).
SetConsoleWindowInfo | BOOL SetConsoleWindowInfo(HANDLE hConsoleOutput, BOOL fAbsolute, PSMALL_RECT psrctWindowRect); | kernel32.dll | Cette fonction permet de fixer la taille courante et la position de la fenêtre du tampon de la console d'écran.
SetCurrentDirectoryA | BOOL SetCurrentDirectoryA(LPCTSTR lpPathName); | kernel32.dll | Cette fonction permet de fixer le répertoire courant du processus courant.
SetCurrentDirectoryW | BOOL SetCurrentDirectoryW(LPCWSTR lpPathName); | kernel32.dll | Cette fonction permet de fixer le répertoire courant du processus courant avec des paramètres de chaine de caractères étendues (Unicode).
SetCursor | HCURSOR SetCursor(HCURSOR hcur); | user32.dll | Cette fonction permet de fixer l'image du pointeur de la souris.
SetCursorPos | BOOL SetCursorPos(int X, int Y); | user32.dll | Cette fonction permet de fixer les coordonnées du pointeur de la souris.
SetDebugErrorLevel | VOID SetDebugErrorLevel(DWORD dwLevel); | user32.dll | Cette fonction permet de fxer le niveau d'erreur minimum dans chaque fenêtre étant généré par les événements de déboguage et passe celui-ci au débogueur.
SetDefaultCommConfigA | BOOL SetDefaultCommConfigA(LPCSTR lpszName, LPCOMMCONFIG lpCC, DWORD dwSize); | kernel32.dll | Cette fonction permet de fixer la configuration par défaut pour un périphérique de communication.
SetDefaultCommConfigW | BOOL SetDefaultCommConfigW(LPCWSTR lpszDevice, LPCOMMCONFIG lpCommConfig, DWORD dwSize); | kernel32.dll | Cette fonction permet de fixer la configuration par défaut pour un périphérique de communication avec des paramètres de chaine de caractères étendues (Unicode).
SetDeviceGammaRamp | BOOL SetDeviceGammaRamp(HDC hdc, LPVOID lpRamp); | gdi32.dll | Cette fonction permet de fixer la rampe gamma d'un panneau d'affichage de couleur directe.
SetDIBColorTable | UINT SetDIBColorTable(HDC hdc, UINT uStartIndex, UINT cEntries, RGBQUAD *pColors); | gdi32.dll | Cette fonction permet de fixer les valeurs de couleurs RVB (rouge, vert, bleu) dans une rangée de table de couleurs d'un périphérique de Bitmap indépendant (DIB) étant actuellement sélectionné dans un périphérique de contexte spécifié.
SetDIBits | int SetDIBits(HDC hdc, HBITMAP hbmp, UINT uStartScan, UINT cScanLines, CONST VOID * lpvBits, CONST BITMAPINFO * lpbmi, UINT fuColorUse); | gdi32.dll | Cette fonction permet de fixer le pixel dans un Bitmap compatible (DDB) en utilisant les données de couleur trouvé dans le DIB spécifié.
SetDIBitsToDevice | int SetDIBitsToDevice(HDC hdc, int XDest, int YDest, DWORD dwWidth, DWORD dwHeight, int XSrc, int YSrc, UINT uStartScan, UINT cScanLines, CONST VOID * lpvBits, CONST BITMAPINFO * lpbmi, UINT fuColorUse); | gdi32.dll | Cette fonction permet de fixer les bits d'un périphérique de Bitmap indépendant (DIB) directement dans un périphérique de surface.
SetDlgItemInt | BOOL SetDlgItemInt(HWND hwndDlg, int idControl, UINT uValue, BOOL fSigned); | user32.dll | Cette fonction permet de fixer le texte d'un contrôle donnée dans une boite de dialogue vers la chaine de caractères de représentation spécifiant la valeur entière.
SetDlgItemTextA | BOOL SetDlgItemTextA(HWND hwndDlg, int idControl, LPCTSTR lpsz); | user32.dll | Cette fonction permet de fixer le titre ou le texte d'un contrôle d'une boite de dialogue.
SetDlgItemTextW | BOOL SetDlgItemTextW(HWND hwndDlg, int idControl, LPCWSTR lpsz); | user32.dll | Cette fonction permet de fixer le titre ou le texte d'un contrôle d'une boite de dialogue avec des paramètres de chaine de caractères étendues (Unicode).
SetDoubleClickTime | BOOL SetDoubleClickTime(UINT uInterval); | user32.dll | Cette fonction permet de fixer le temps maximal autorisé pour que des clics sur les boutons de la souris soit considéré comme un double-clic.
SetEndOfFile | BOOL SetEndOfFile(HANDLE hFile); | kernel32.dll | Cette fonction permet de fixer la position du pointeur de fichier à la fin du fichier utilisant un Handle.
SetEnhMetaFileBits | HENHMETAFILE SetEnhMetaFileBits(UINT cbBuffer, CONST BYTE * lpData); | gdi32.dll | Cette fonction permet de créer un fichier méta de format étendue, basé en mémoire, de données fournit.
SetEnvironmentVariableA | BOOL SetEnvironmentVariableA(LPCTSTR lpszName, LPCTSTR lpszValue); | kernel32.dll | Cette fonction permet de fixer la valeur d'une variable d'environnement du système d'exploitation.
SetEnvironmentVariableW | BOOL SetEnvironmentVariableW(LPCWSTR lpszName, LPCWSTR lpszValue); | kernel32.dll | Cette fonction permet de fixer la valeur d'une variable d'environnement du système d'exploitation avec des paramètres de chaine de caractères étendues (Unicode).
SetErrorMode | UINT SetErrorMode(UINT fuErrorMode); | kernel32.dll | Cette fonction permet de contrôler comment le système d'exploitation gère les types sévères dans les erreurs sérieuses.
SetEvent | BOOL SetEvent(HANDLE hEvent); | kernel32.dll | Cette fonction permet de fixer l'état de l'objet d'événement spécifié par un signal.
SetFileApisToANSI | VOID SetFileApisToANSI(VOID); | kernel32.dll | Cette fonction permet de faire provoquer, à une série de fonctions de fichiers Win32, l'utilisation d'un ensemble de code de page de caractères ANSI.
SetFileApisToOEM | VOID SetFileApisToOEM(VOID); | kernel32.dll | Cette fonction permet de faire provoquer, à une série de fonctions de fichiers Win32, l'utilisation d'un ensemble de code de page de caractères OEM.
SetFileAttributesA | BOOL SetFileAttributesA(LPCTSTR lpFileName, DWORD dwFileAttributes); | kernel32.dll | Cette fonction permet de fixer les attributs d'un fichier ou d'un répertoire.
SetFileAttributesW | BOOL SetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes); | kernel32.dll | Cette fonction permet de fixer les attributs d'un fichier ou d'un répertoire avec des paramètres de chaine de caractères étendues (Unicode).
SetFilePointer | DWORD SetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod); | kernel32.dll | Cette fonction permet de déplacer un pointeur de ficher dans un fichier ouvert.
SetFileSecurityA | BOOL SetFileSecurityA( LPCTSTR lpszFile, SECURITY_INFORMATION si, PSECURITY_DESCRIPTOR psd ); | advapi32.dll | Cette fonction permet de fixer la sécurité d'un objet de fichier ou d'un répertoire.
SetFileSecurityW | BOOL SetFileSecurityW( LPCWSTR lpFileName, SECURITY_INFORMATION RequestedInformation, PSECURITY_DESCRIPTOR pSecurityDescriptor ); | advapi32.dll | Cette fonction permet de fixer la sécurité d'un objet de fichier ou d'un répertoire avec des paramètres de chaine de caractères étendue (Unicode).
SetFileTime | BOOL SetFileTime(HANDLE hFile, CONST FILETIME * lpftCreation, CONST FILETIME * lpftLastAccess, CONST FILETIME * lpftLastWrite); | kernel32.dll | Cette fonction permet de fixer la date et l'heure de la création, du dernière accès et de la dernière modification d'un fichier.
SetFocus | HWND SetFocus(HWND hwnd); | user32.dll | Cette fonction permet de fixer le focus d'entrée de la fenêtre spécifié.
SetForegroundWindow | BOOL SetForegroundWindow(HWND hwnd); | user32.dll | Cette fonction permet de mettre le processus léger étant créer pour la fenêtre spécifié dans une fenêtre active ou de premier plan.
SetGraphicsMode | int SetGraphicsMode(HDC hdc, int iMode); | gdi32.dll | Cette fonction permet de fixer le mode graphique pour le périphérique de contexte spécifié.
SetHandleCount | UINT SetHandleCount(UINT cHandles); | kernel32.dll | Cette fonction permet de changer le nombre de fichier Handle disponible pour un processus.
SetHandleInformation | BOOL SetHandleInformation(HANDLE hObject, DWORD dwMask, DWORD dwFlags); | kernel32.dll | Cette fonction permet de fixer certaines propriétés d'un objet Handle.
SetICMMode | int SetICMMode(HDC hdc, int fICM); | gdi32.dll | Cette fonction permet d'activer ou désactiver la correspondance de couleur d'image pour le périphérique de contexte spécifié.
SetICMProfileA | BOOL SetICMProfileA(HDC hdc, LPTSTR lpFileName); | gdi32.dll | Cette fonction permet de fixer la couleur de profile pour le périphérique de contexte spécifié.
SetICMProfileW | BOOL SetICMProfileW(HDC hdc, LPWSTR lpFileName); | gdi32.dll | Cette fonction permet de fixer la couleur de profile pour le périphérique de contexte spécifié avec des paramètres de chaine de caractères étendues (Unicode).
SetJobA | BOOL SetJobA(HANDLE hPrinter, DWORD dwJob, DWORD dwLevel, LPBYTE lpbJob, DWORD dwCommand); | winspool.drv | Cette fonction permet de fixer l'état du traitement d'un travail contenu dans le tampon d'impressions de l'imprimante.
SetKeyboardState | BOOL SetKeyboardState(LPBYTE lpbKeyState); | user32.dll | Cette fonction permet de fixer l'état des 256 touches virtuel de clavier.
SetKernelObjectSecurity | BOOL SetKernelObjectSecurity( HANDLE hObject, SECURITY_INFORMATION si, PSECURITY_DESCRIPTOR psd ); | advapi32.dll | Cette fonction permet de fixer la sécurité de l'objet du noyau du système d'exploitation.
SetLastError | VOID SetLastError(DWORD fdwError); | kernel32.dll | Cette fonction permet de fixer le code de la dernière erreur d'un appel de processus léger.
SetLastErrorEx | VOID SetLastErrorEx(DWORD fdwError, DWORD fdwType); | user32.dll | Cette fonction permet de fixer le code de la dernière erreur avec un type d'erreur.
SetLocalTime | BOOL SetLocalTime(CONST SYSTEMTIME * lpst); | kernel32.dll | Cette fonction permet de fixer la date et l'heure locale courante.
SetLocaleInfoA | BOOL SetLocaleInfoA(LCID Locale, LCTYPE LCType, LPCTSTR lpLCData); | kernel32.dll | Cette fonction permet de fixer un item d'information local.
SetLocaleInfoW | BOOL SetLocaleInfoW(LCID lcid, LCTYPE lctype, LPCWSTR data); | kernel32.dll | Cette fonction permet de fixer un item d'information local avec des paramètres de chaine de caractères étendues (Unicode).
SetMailslotInfo | BOOL SetMailslotInfo(HANDLE hMailslot, DWORD dwReadTimeout); | kernel32.dll | Cette fonction permet de fixer la valeur de dépassement de délai utilisable pour le mailslot spécifié d'une opération de lecture.
SetMapMode | int SetMapMode(HDC hdc, int fnMapMode); | gdi32.dll | Cette fonction permet de fixer le mode de cartographie d'un périphérique de contexte spécifié.
SetMapperFlags | DWORD SetMapperFlags(HDC hdc, DWORD dwFlag); | gdi32.dll | Cette fonction permet de changer la méthode utilisé pour la cartographie d'une police de caractères lors d'une conversion de police de caractères logique vers une police de caractères physique.
SetMenu | BOOL SetMenu(HWND hwnd, HMENU hmenu); | user32.dll | Cette fonction permet d'attribué un menu à une fenêtre.
SetMenuContextHelpId | BOOL SetMenuContextHelpId(HMENU hmenu, DWORD dwContextHelpId); | user32.dll | Cette fonction permet d'associer un identificateur de contexte d'aide avec un menu.
SetMenuDefaultItem | BOOL SetMenuDefaultItem(HMENU hMenu, UINT uItem, UINT fByPos); | user32.dll | Cette fonction permet de fixer l'item par défaut d'un menu déroulant.
SetMenuItemBitmaps | BOOL SetMenuItemBitmaps(HMENU hmenu, UINT uItem, UINT fuFlags, HBITMAP hbmUnchecked, HBITMAP hbmChecked); | user32.dll | Cette fonction permet d'associer le Bitmap donnée avec un item de menu.
SetMenuItemInfoA | BOOL SetMenuItemInfoA(HMENU hMenu, UINT uItem, BOOL fByPosition, LPMENUITEMINFO lpmii); | user32.dll | Cette fonction permet de modifier les informations conçernant un item de menu.
SetMenuItemInfoW | BOOL SetMenuItemInfoW(HMENU hMenu, UINT uItem, BOOL fByPosition, LPCMENUITEMINFOW lpmii); | user32.dll | Cette fonction permet de modifier les informations conçernant un item de menu avec des paramètres de chaine de caractères étendues (Unicode).
SetMessageQueue | BOOL SetMessageQueue(int cMessagesMax); | user32.dll | Cette fonction permet de créer un nouveau message dans la file d'attente.
SetMetaFileBitsEx | HMETAFILE SetMetaFileBitsEx(UINT nSize, CONST BYTE * lpData); | gdi32.dll | Cette fonction permet de créer un fichier méta de format Windows, basé en mémoire, pour les données fournit.
SetMetaRgn | int SetMetaRgn(HDC hdc); | gdi32.dll | Cette fonction permet d'effectuer l'intersection de la région de capture courante pour le périphérique de contexte spécifié avec la région méta courante et sauvegarde la combinaison des régions dans une nouvelle région méta.
SetMiterLimit | BOOL SetMiterLimit(HDC hdc, FLOAT eNewLimit, PFLOAT peOldLimit); | gdi32.dll | Cette fonction permet de fixer la limite pour la longueur d'une jointure de mitre pour le périphérique de contexte spécifié.
SetNamedPipeHandleState | BOOL SetNamedPipeHandleState(HANDLE hNamedPipe, LPDWORD lpdwMode, LPDWORD lpcbMaxCollect, LPDWORD lpdwCollectDataTimeout); | kernel32.dll | Cette fonction permet de fixer le mode de lecture et le mode de bloc d'un tuyau nommé spécifié.
SetPaletteEntries | UINT SetPaletteEntries(HPALETTE hpal, UINT iStart, UINT cEntries, CONST PALETTEENTRY *lppe); | gdi32.dll | Cette fonction permet de fixer les valeurs de couleurs RVB (ou RGB en anglais) et les drapeaux d'un rang des entrées dans une palette logique spécifié.
SetParent | HWND SetParent(HWND hwndChild, HWND hwndNewParent); | user32.dll | Cette fonction permet de transférer un objet d'une fenêtre vers une autre.
SetPixel | COLORREF SetPixel(HDC hdc, int X, int Y, COLORREF crColor); | gdi32.dll | Cette fonction permet de fixer la couleur sous format RVB (RGB) d'un pixel affiché à l'écran.
SetPixelFormat | BOOL SetPixelFormat(HDC hdc, int iPixelFormat, const PIXELFORMATDESCRIPTOR *ppfd); | gdi32.dll | Cette fonction permet de fixer le format de pixel du périphérique de contexte spécifié au format spécifié par l'index spécifié.
SetPixelV | BOOL SetPixelV(HDC hdc, int X, int Y, COLORREF crColor); | gdi32.dll | Cette fonction permet de fixer le pixel aux coordonnées de fermeture approximatifs spécifiées avec la couleur spécifié.
SetPolyFillMode | int SetPolyFillMode(HDC hdc, int iPolyFillMode); | gdi32.dll | Cette fonction permet de fixer le mode de remplissage d'un polygone spécifié.
SetPrinterA | BOOL SetPrinterA(HANDLE hPrinter, DWORD dwLevel, LPBYTE lpbPrinter, DWORD dwCommand); | winspool.drv | Cette fonction permet d'envoyer une commande à l'imprimante.
SetPriorityClass | BOOL SetPriorityClass(HANDLE hProcess, DWORD fdwPriority); | kernel32.dll | Cette fonction permet de fixer la priorité d'une classe pour le processus spécifié.
SetPrivateObjectSecurity | BOOL SetPrivateObjectSecurity( SECURITY_INFORMATION si, PSECURITY_DESCRIPTOR psdSource, PSECURITY_DESCRIPTOR *lppsdTarget, PGENERIC_MAPPING pgm, HANDLE hClientToken ); | advapi32.dll | Cette fonction permet de modifier un descripteur de sécurité d'objet privé.
SetProcessAffinityMask | BOOL SetProcessAffinityMask(HANDLE hProcess, DWORD_PTR dwProcessAffinityMask); | kernel32.dll | Cette fonction permet de fixer un masque d'affinité de processeur pour le processus léger d'un processus spécifié.
SetProcessPriorityBoost | BOOL SetProcessPriorityBoost(HANDLE hProcess, BOOL DisablePriorityBoost); | kernel32.dll | Cette fonction permet d'activer ou désactiver les habilitées du système pour stimuler temporairement la priorité d'un processus léger dans un processus spécifié.
SetProcessShutdownParameters | BOOL SetProcessShutdownParameters(DWORD dwLevel, DWORD dwFlags); | kernel32.dll | Cette fonction permet de fixer les paramètres de fermeture du système pour le processus appelant courant.
SetProcessWindowStation | BOOL SetProcessWindowStation(HWINSTA hWinSta); | user32.dll | Cette fonction permet de fixer une fenêtre de station vers un processus appellant.
SetProcessWorkingSetSize | BOOL SetProcessWorkingSetSize(HANDLE hProcess, DWORD dwMinimumWorkingSetSize, DWORD dwMaximumWorkingSetSize); | kernel32.dll | Cette fonction permet de fixer la taille du travail minimum et maximum d'un processus spécifié.
SetPropA | BOOL SetPropA(HWND hwnd, LPCTSTR lpsz, HANDLE hData); | user32.dll | Cette fonction permet d'ajouter une nouvelle entrée ou changer une entrée existante dans la liste de propriété d'une fenêtre spécifié.
SetPropW | BOOL SetPropW(HWND hwnd, LPCWSTR lpsz, HANDLE hData); | user32.dll | Cette fonction permet d'ajouter une nouvelle entrée ou changer une entrée existante dans la liste de propriété d'une fenêtre spécifié avec des paramètres de chaine de caractères étendues (Unicode).
SetRect | BOOL SetRect(LPRECT lprc, int nLeft, int nTop, int nRight, int nBottom); | user32.dll | Cette fonction permet de fixer la taille et la position d'un rectangle.
SetRectEmpty | BOOL SetRectEmpty(LPRECT lprc); | user32.dll | Cette fonction permet de créer un rectangle vide (tous les coordonnées sont fixées à 0).
SetRectRgn | BOOL SetRectRgn(HRGN hrgn, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect); | gdi32.dll | Cette fonction permet de changer la région spécifié dans un région rectangulaire avec les coordonnées spécifiés.
SetROP2 | int SetROP2(HDC hdc, int fnDrawMode); | gdi32.dll | Cette fonction permet de fixer le mode de dessinage courant.
SetScrollInfo | int SetScrollInfo(HWND hwnd, int fnBar, LPSCROLLINFO lpsi, BOOL fRedraw); | user32.dll | Cette fonction permet de fixer les paramètres de barre de défilement, en incluant les positions minimums et maximums, la taille de la page et la position de la boite de défilement.
SetScrollPos | int SetScrollPos(HWND hwnd, int fnBar, int nPos, BOOL fRedraw); | user32.dll | Cette fonction permet de fixer la position d'une boite de défilement et, si requis, redessiner la barre de défilement reflétant la nouvelle position de la boite de défilement.
SetScrollRange | BOOL SetScrollRange(HWND hwnd, int fnBar, int nMinPos, int nMaxPos, BOOL fRedraw); | user32.dll | Cette fonction permet de fixer les valeurs de position minimum et maximum de la barre de défilement spécifié.
SetSecurityDescriptorDacl | BOOL SetSecurityDescriptorDacl( PSECURITY_DESCRIPTOR psd, BOOL fDaclPresent, PACL pAcl, BOOL fDaclDefaulted ); | advapi32.dll | Cette fonction permet de fixer les informations dans une liste de contrôle d'accès (ACL) discrétionnaire.
SetSecurityDescriptorGroup | BOOL SetSecurityDescriptorGroup( PSECURITY_DESCRIPTOR psd, PSID psidGroup, BOOL fGroupDefaulted ); | advapi32.dll | Cette fonction permet de fixer le groupe primaire d'information d'un format absolue de descripteur de sécurité, remplacer par un groupe d'information primaire déjà présent dans un descripteur de sécurité.
SetSecurityDescriptorOwner | BOOL SetSecurityDescriptorOwner( PSECURITY_DESCRIPTOR psd, PSID psidOwner, BOOL fOwnerDefaulted ); | advapi32.dll | Cette fonction permet de fixer les informations de propriétaire d'un format absolue de descripteur de sécurité.
SetSecurityDescriptorSacl | BOOL SetSecurityDescriptorSacl( PSECURITY_DESCRIPTOR psd, BOOL fSaclPresent, PACL pAcl, BOOL fSaclDefaulted ); | advapi32.dll | Cette fonction permet de fixer les informations d'une liste de contrôle d'accès (ACL) système.
SetStdHandle | BOOL SetStdHandle(DWORD IDStdHandle, HANDLE hHandle); | kernel32.dll | Cette fonction permet d'utiliser un ensemble de gestionnaire pour l'entrée standard, la sortie standard ou le périphérique d'erreur standard.
SetStretchBltMode | int SetStretchBltMode(HDC hdc, int iStretchMode); | gdi32.dll | Cette fonction permet de fixer le mode d'étirement Bitmap.
SetSysColors | BOOL WINAPI SetSysColors(int cDspElements, CONST INT *lpnDspElements, CONST COLORREF *lpdwRgbValues); | user32.dll | Cette fonction permet de fixer la/les couleurs d'un/des objet(s) affiché(s) du système d'exploitations Windows.
SetSystemCursor | BOOL SetSystemCursor(HCURSOR hCursor, DWORD dwCursorID); | user32.dll | Cette fonction permet de modifier un des curseurs qu'utilise le système d'exploitations Windows.
SetSystemPaletteUse | UINT SetSystemPaletteUse(HDC hdc, UINT uUsage); | gdi32.dll | Cette fonction permet de fixer l'utilisation de la couleur statique dans une palette du système.
SetSystemPowerState | BOOL SetSystemPowerState(BOOL fSuspend, BOOL fForce); | kernel32.dll | Cette fonction permet de fixer l'état de l'alimentation en énergie de l'ordinateur.
SetSystemTime | BOOL SetSystemTime(CONST SYSTEMTIME * lpst); | kernel32.dll | Cette fonction permet de fixer la date et l'heure système courante.
SetSystemTimeAdjustment | BOOL SetSystemTimeAdjustment(DWORD dwTimeAdjustment, PBOOL bTimeAdjustmentDisabled); | kernel32.dll | Cette fonction permet d'activer ou désactiver l'ajustement du temps périodique de l'horloge de l'heure de la journée.
SetTapeParameters | DWORD SetTapeParameters(HANDLE hDevice, DWORD dwOperation, LPVOID lpTapeInformation); | kernel32.dll | Cette fonction permet de fixer la taille d'un bloc de bande magnétique ou configure le périphérique de bande magnétique.
SetTapePosition | DWORD SetTapePosition(HANDLE hDevice, DWORD dwPositionMethod, DWORD dwPartition, DWORD dwOffsetLow, DWORD dwOffsetHigh, BOOL bImmediate); | kernel32.dll | Cette fonction permet de fixer la position de la bande magnétique dans un périphérique spécifié.
SetTextAlign | UINT SetTextAlign(HDC hdc, UINT fMode); | gdi32.dll | Cette fonction permet de fixer le contexte d'alignement d'un objet de texte.
SetTextColor | COLORREF SetTextColor(HDC hdc, COLORREF crColor); | gdi32.dll | Cette fonction permet de fixer la couleur du texte de la couleur spécifié.
SetTextCharacterExtra | int SetTextCharacterExtra(HDC hdc, int nCharExtra); | gdi32.dll | Cette fonction permet de fixer le quantité d'espacement entre les caractères.
SetTextJustification | BOOL SetTextJustification(HDC hdc, int nBreakExtra, int nBreakCount); | gdi32.dll | Cette fonction permet d'ajouter de l'espacement dans les caractères de saut d'une chaine de caractères.
SetThreadAffinityMask | DWORD SetThreadAffinityMask(HANDLE hThread, DWORD dwThreadAffinityMask); | kernel32.dll | Cette fonction permet de fixer le masque d'affinité du microprocesseur pour le processus léger spécifié.
SetThreadContext | BOOL SetThreadContext(HANDLE hThread, CONST CONTEXT * lpContext); | kernel32.dll | Cette fonction permet de fixer le contexte d'un processus léger spécifié.
SetThreadDesktop | BOOL SetThreadDesktop(HDESK hDesk); | user32.dll | Cette fonction permet de fixer le bureau au processus léger appellant.
SetThreadIdealProcessor | DWORD SetThreadIdealProcessor(HANDLE hThread, DWORD dwIdealProcessor); | kernel32.dll | Cette fonction permet de fixer le microprocesseur préféré pour un processus léger.
SetThreadLocale | BOOL SetThreadLocale(LCID Locale); | kernel32.dll | Cette fonction permet de fixer l'appel de processus léger locale courant.
SetThreadPriority | BOOL SetThreadPriority(HANDLE hThread, int nPriority); | kernel32.dll | Cette fonction permet de fixer la valeur de priorité pour le processus léger spécifié.
SetThreadPriorityBoost | BOOL SetThreadPriorityBoost(HANDLE hThread, BOOL DisablePriorityBoost); | kernel32.dll | Cette fonction permet d'activer ou désactiver l'habilité d'un système à temporairement stimuler la priorité d'un processus léger.
SetThreadToken | BOOL SetThreadToken( PHANDLE Thread, HANDLE Token ); | advapi32.dll | Cette fonction permet de fixer une personnification de jeton à un processus léger.
SetTimer | UINT SetTimer(HWND hwnd, UINT idTimer, UINT uTimeout, TIMERPROC tmprc); | user32.dll | Cette fonction permet d'installer une minuterie système.
SetTimeZoneInformation | BOOL SetTimeZoneInformation(CONST TIME_ZONE_INFORMATION * lptzi); | kernel32.dll | Cette fonction permet de fixer les paramètres de fuseau horaire courant.
SetTokenInformation | BOOL SetTokenInformation( HANDLE hToken, TOKEN_INFORMATION_CLASS tic, LPVOID lpvInformation, DWORD cbInformation ); | advapi32.dll | Cette fonction permet de fixer une variété de type d'information pour un accès à un jeton spécifié.
SetUnhandledExceptionFilter | LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter); | kernel32.dll | Cette fonction permet à une application de remplacer le gestionnaire d'exception de bas niveau dans les places Win32 du haut de chaque processus léger et processus.
SetupComm | BOOL SetupComm(HANDLE hCommDev, DWORD cbInQueue, DWORD cbOutQueue); | kernel32.dll | Cette fonction permet d'initialiser les paramètres de communications pour un périphérique de communications spécifié.
SetupCopyErrorA | UINT SetupCopyErrorA( HWND hwndParent, PCTSTR DialogTitle, PCTSTR DiskName, PCTSTR PathToSource, PCTSTR SourceFile, PCTSTR TargetPathFile, UINT Win32ErrorCode, DWORD Style, PTSTR PathBuffer, DWORD PathBufferSize, PDWORD PathRequiredSize ); | setupapi.dll | Cette fonction permet d'afficher une boite de dialogue pour indiquer à l'utilisateur qu'une erreur pendant la copie s'est produit.
SetupDeleteErrorA | UINT SetupDeleteErrorA( HWND hwndParent, PCTSTR DialogTitle, PCTSTR File, UINT Win32ErrorCode, DWORD Style ); | setupapi.dll | Cette fonction permet d'afficher une boite de dialogue pour indiquer à l'utilisateur qu'une erreur pendant l'effacement d'un fichier s'est produit.
SetUserObjectInformationA | BOOL SetUserObjectInformationA(HANDLE hObject, int nIndex, PVOID lpvInfo, DWORD cbInfo); | user32.dll | Cette fonction permet de fixer les informations à propos d'une fenêtre de station ou d'un objet de bureau.
SetUserObjectInformationW | BOOL SetUserObjectInformationW(HANDLE hObject, int nIndex, PVOID lpvInfo, DWORD cbInfo); | user32.dll | Cette fonction permet de fixer les informations à propos d'une fenêtre de station ou d'un objet de bureau avec des paramètres de chaine de caractères étendues (Unicode).
SetUserObjectSecurity | BOOL SetUserObjectSecurity(HANDLE hObject, PSECURITY_INFORMATION psi, PSECURITY_DESCRIPTOR psd); | user32.dll | Cette fonction permet de fixer la sécurité d'un objet utilisateur.
SetViewportExtEx | BOOL SetViewportExtEx(HDC hdc, int nXExtent, int nYExtent, LPSIZE lpSize); | gdi32.dll | Cette fonction permet de fixer les extensions X et Y d'un point de vue d'un périphérique de contexte spécifié.
SetViewportOrgEx | BOOL SetViewportOrgEx(HDC hdc, int X, int Y, LPPOINT lpPoint); | gdi32.dll | Cette fonction permet de fixer le point de vue d'origine d'un périphérique de contexte spécifié.
SetVolumeLabelA | BOOL SetVolumeLabelA(LPCTSTR lpRootPathName, LPCTSTR lpVolumeName); | kernel32.dll | Cette fonction permet de fixer le nom de volume d'une unité de disque.
SetVolumeLabelW | BOOL SetVolumeLabelW(LPCWSTR lpRootPathName, PCWSTR lpVolumeName); | kernel32.dll | Cette fonction permet de fixer le nom de volume d'une unité de disque avec des paramètres de chaine de caractères étendues (Unicode).
SetWaitableTimer | BOOL SetWaitableTimer(HANDLE hTimer, const LARGE_INTEGER *pDueTime, LONG lPeriod, PTIMERAPCROUTINE pfnCompletionRoutine, LPVOID lpArgToCompletionRoutine, BOOL fResume); | kernel32.dll | Cette fonction permet d'activer la minuterie d'attente spécifié.
SetWindowContextHelpId | BOOL SetWindowContextHelpId(HWND hwnd, DWORD dwContextHelpId); | user32.dll | Cette fonction permet d'associer un identificateur de contexte d'aide avec la fenêtre spécifié.
SetWindowLongA | LONG SetWindowLongA(HWND hwnd, int nIndex, LONG lNewLong); | user32.dll | Cette fonction permet de placer une valeur 32 bits dans le déplacement spécifié dans une fenêtre en mémoire supplémentaire pour la fenêtre spécifié.
SetWindowLongW | LONG SetWindowLongW(HWND hwnd, int nIndex, LONG lNewLong); | user32.dll | Cette fonction permet de placer une valeur 32 bits dans le déplacement spécifié dans une fenêtre en mémoire supplémentaire pour la fenêtre spécifié avec des paramètres de chaine de caractères étendues (Unicode).
SetWindowPlacement | BOOL SetWindowPlacement(HWND hwnd, CONST WINDOWPLACEMENT * lpwndpl); | user32.dll | Cette fonction permet de fixer l'état d'affichage et de restituer, minimiser et maximiser les positions d'une fenêtre.
SetWindowPos | BOOL SetWindowPos(HWND hwnd, HWND hwndInsertAfter, int x, int y, int cx, int cy, UINT fuFlags); | user32.dll | Cette fonction permet de fixer la position d'une fenêtre à une nouvelle coordonnées à l'écran.
SetWindowRgn | int SetWindowRgn(HWND hWnd, HRGN hRgn, BOOL bRedraw); | user32.dll | Cette fonction permet de fixer la région de fenêtre dans une fenêtre.
SetWindowsHookA | HHOOK SetWindowsHookA(int idHook, HOOKPROC hkprc); | user32.dll | Cette fonction permet d'installer une fonction de crochet utilisateur dans une chaine de crochet.
SetWindowsHookExA | HHOOK SetWindowsHookExA(int idHook, HOOKPROC hkprc, HINSTANCE hMod, DWORD dwThreadID); | user32.dll | Cette fonction permet d'installer un crochet de fonction utilisateur dans une chaine de crochet.
SetWindowsHookExW | HHOOK SetWindowsHookExW(int idHook, HOOKPROC hkprc, HINSTANCE hMod, DWORD dwThreadID); | user32.dll | Cette fonction permet d'installer un crochet de fonction utilisateur dans une chaine de crochet avec des paramètres de chaine de caractères étendues (Unicode).
SetWindowsHookW | HHOOK SetWindowsHookW(int idHook, HOOKPROC hkprc); | user32.dll | Cette fonction permet d'installer une fonction de crochet utilisateur dans une chaine de crochet avec des paramètres de chaine de caractères étendues (Unicode).
SetWindowTextA | BOOL SetWindowTextA(HWND hwnd, LPCTSTR lpsz); | user32.dll | Cette fonction permet de définir le contenu texte d'une fenêtre.
SetWindowTextW | BOOL SetWindowTextW(HWND hwnd, LPCWSTR lpsz); | user32.dll | Cette fonction permet de définir le contenu texte d'une fenêtre avec des paramètres de chaine de caractères étendues (Unicode).
SetWindowWord | WORD SetWindowWord(HWND hwnd, int nIndex, WORD wNewWord); | user32.dll | Cette fonction permet de placer une valeur 16 bits dans le déplacement spécifié dans une fenêtre en mémoire supplémentaire pour la fenêtre spécifié.
SetWinMetaFileBits | HENHMETAFILE SetWinMetaFileBits(UINT cbBuffer, CONST BYTE * lpbBuffer, HDC hdcRef, CONST METAFILEPICT * lpmfp); | gdi32.dll | Cette fonction permet de convertir un fichier méta d'un ancien format Windows vers un nouveau format étendue et entrepose le fichier méta en mémoire.
SetWindowOrgEx | BOOL SetWindowOrgEx(HDC hdc, int X, int Y, LPPOINT lpPoint); | gdi32.dll | Cette fonction permet de fixer la fenêtre d'origine d'un périphérique de contexte spécifié.
SetWorldTransform | BOOL SetWorldTransform(HDC hdc, CONST XFORM * lpXform); | gdi32.dll | Cette fonction permet de fixer une transformation de double dimensions linéaire entre l'espace mondiale et l'espace de page pour le périphérique de contexte spécifié.
SHAddToRecentDocs | WINSHELLAPI void WINAPI SHAddToRecentDocs(UINT uFlags, LPCVOID pv); | shell32.dll | Cette fonction permet d'ajouter un lien vers un document dans la liste des documents récamment utilisé.
SHAppBarMessage | WINSHELLAPI UINT APIENTRY SHAppBarMessage(DWORD dwMessage, PAPPBARDATA pabd); | shell32.dll | Cette fonction permet d'envoyer des commandes à la barre de tâche.
SHBrowseForFolder | WINSHELLAPI LPITEMIDLIST WINAPI SHBrowseForFolder(LPBROWSEINFO lpbi); | shell32.dll | Cette fonction permet d'afficher la boite de dialogue sélectionnant un répertoire.
SHCopyKeyA | DWORD SHCopyKeyA(HKEY hKeySrc, LPCSTR lpszSrcSubKey, HKEY hKeyDst, DWORD dwReserved); | shell32.dll | Cette fonction permet de copier les clefs de registre d'un répertoire à un autre dans la base de registres en tenant compte de bien copier toutes les sous-branches de répertoire inclus dans la clef source.
ShellAboutA | int ShellAboutA(HWND hWnd, LPCTSTR szApp, LPCTSTR szOtherStuff, HICON hIcon); | shell32.dll | Cette fonction permet d'afficher la boite de dialogue «A propos» de l'application spécifié.
ShellExecuteA | HINSTANCE ShellExecuteA( HWND hwnd, LPCTSTR lpOperation, LPCTSTR lpFile, LPTSTR lpParameters, LPCTSTR lpDirectory, INT nShowCmd ); | shell32.dll | Cette fonction permet de lancer une application externe.
ShellExecuteEx | WINSHELLAPI BOOL WINAPI ShellExecuteEx(LPSHELLEXECUTEINFO lpExecInfo); | shell32.dll | Cette fonction permet de lancer une application externe en utilisant l'interpréteur de commande (Shell) du système d'exploitation Windows.
Shell_NotifyIconA | BOOL Shell_NotifyIconA( DWORD dwMessage, PNOTIFYICONDATA lpdata); | shell32.dll | Cette fonction permet d'effectuer l'action d'ajouter, modifier ou supprimer une icône dans la barre de tâche.
SHEmptyRecycleBinA | HRESULT SHEmptyRecycleBinA( HWND hwnd, LPCTSTR pszRootPath, DWORD dwFlags); | shell32.dll | Cette fonction permet de vider le contenu de la corbeille.
SHFileOperationA | WINSHELLAPI int WINAPI SHFileOperationA(LPSHFILEOPSTRUCT lpFileOp); | shell32.dll | Cette fonction permet d'effectuer une copie, renommer, déplacer ou de supprimer un objet de fichier.
SHFormatDrive | DWORD SHFormatDrive( HWND hwnd, UINT drive, UINT fmtID, UINT options); | shell32.dll | Cette fonction permet d'effectuer le formattage d'une unité de disque.
SHGetPathFromIDList | WINSHELLAPI BOOL WINAPI SHGetPathFromIDList(LPCITEMIDLIST pidl, LPSTR pszPath); | shell32.dll | Cette fonction permet de retourner un pointeur dans un arbre de répertoire à l'emplacement ou se trouve un fichier.
ShowCaret | BOOL ShowCaret(HWND hwnd); | user32.dll | Cette fonction permet d'afficher un caret à l'écran à la position courant du caret.
ShowCursor | int ShowCursor( BOOL bShow); | user32.dll | Cette fonction permet d'afficher ou de faire disparaitre le pointeur de la souris.
ShowOwnedPopups | BOOL ShowOwnedPopups(HWND hwnd, BOOL fShow); | user32.dll | Cette fonction permet d'afficher ou de cacher tous les fenêtres POP-UP du propriétaire de la fenêtre spécifié.
ShowScrollBar | BOOL ShowScrollBar(HWND hwnd, int fnBar, BOOL fShow); | user32.dll | Cette fonction permet d'afficher ou de faire disparaitre la barre de défilement d'une fenêtre ou d'un objet.
ShowWindow | BOOL ShowWindow( HWND hwnd, int nCmdShow); | user32.dll | Cette fonction permet de définir l'état visuel d'une fenêtre d'application.
ShowWindowAsync | BOOL ShowWindowAsync(HWND hwnd, int nCmdShow); | user32.dll | Cette fonction permet de fixer l'affichage d'état de la création d'une fenêtre par différent processus léger.
SHSetValueA | DWORD SHSetValueA(HKEY hKey, LPCSTR lpszSubKey, LPCSTR lpszValue, DWORD dwType, LPCVOID pvData, DWORD cbData); | shlwapi.dll | Cette fonction permet de fixer une valeur dans la base de registres.
SignalObjectAndWait | DWORD SignalObjectAndWait(HANDLE hObjectToSignal, HANDLE hObjectToWaitOn, DWORD dwMilliseconds, BOOL bAlertable); | kernel32.dll | Cette fonction permet de signaler un objet et d'attendre après un autre objet, une simple opération.
SizeofResource | DWORD SizeofResource(HINSTANCE hModule, HRSRC hrsrc); | kernel32.dll | Cette fonction permet de demander la taille, en octets, d'une ressource spécifié.
Sleep | VOID Sleep(DWORD cMilliseconds); | kernel32.dll | Cette fonction permet de faire passer en mode d'attente pendant un certain délai en milliseconde le processus courant.
SleepEx | DWORD SleepEx(DWORD dwTimeout, BOOL fAlertable); | kernel32.dll | Cette fonction permet de faire en sorte que le processus léger courant entre dans un état d'attente jusqu'à ce que l'intervalle de temps spécifié est passé ou jusqu'à l'achèvement d'une fonction d'appel arrière d'entrée/sortie soit appelé.
sndPlaySoundA | BOOL sndPlaySoundA(LPCSTR lpszSound, UINT fuSound); | winmm.dll | Cette fonction permet de faire jouer un son de format Wave à partir d'un fichier.
SQLAllocConnect | RETCODE SQLAllocConnect(HENV henv, HDBC FAR * phdbc); | odbc32.dll | Cette fonction permet d'effectuer de l'allocation de mémoire pour un identificateur de connexion ODBC avec un environnement spécifié.
SQLAllocEnv | RETCODE SQLAllocEnv(HENV FAR *phenv); | odbc32.dll | Cette fonction permet d'effectuer de l'allocation de mémoire pour un identificateur d'environnement ODBC.
SQLAllocStmt | RETCODE SQLAllocStmt(HDBC hdbc, HSTMT FAR * phstmt); | odbc32.dll | Cette fonction permet d'effectuer de l'allocation de mémoire pour un identificateur d'instruction ODBC.
SQLBindCol | UL_FN_SPEC SQLRETURN UL_FN_MOD SQLBindCol( SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, SQLPOINTER TargetValue, SQLLEN BufferLength, SQLLEN * pcbValue); | odbc32.dll | Cette fonction permet d'effectuer de la lecture d'une colonne du résultat d'une requête par ODBC.
SQLBindParameter | SQLRETURN SQLBindParameter( SQLHSTMT StatementHandle, SQLUSMALLINT ParameterNumber, SQLSMALLINT InputOutputType, SQLSMALLINT ValueType, SQLSMALLINT ParameterType, SQLUINTEGER ColumnSize, SQLSMALLINT DecimalDigits, SQLPOINTER ParameterValuePtr, SQLINTEGER BufferLength, SQLINTEGER * StrLen_or_IndPtr); | odbc32.dll | Cette fonction permet d'envoyer un tampon de paramètres dans une instruction SQL par ODBC.
SQLConfigDataSource | BOOL SQLConfigDataSource( HWND hwndParent, WORD fRequest, LPCSTR lpszDriver, LPCSTR lpszAttributes); | odbccp32.dll | Cette fonction permet d'envoyer des commandes au pilote ODBC (Open Data Base Connectivity).
StartDocA | int StartDocA( HDC hdc, CONST DOCINFO * lpdi); | gdi32.dll | Cette fonction permet d'indiquer le commencement d'un travail d'impression pour l'imprimante.
StartDocW | int StartDocW(HDC hdc, CONST DOCINFOW * lpdi); | gdi32.dll | Cette fonction permet d'indiquer le commencement d'un travail d'impression pour l'imprimante avec des paramètres de chaine de caractères étendues (Unicode).
StartPage | int StartPage(HDC hDC); | gdi32.dll | Cette fonction permet d'indiquer le début d'une page de document pour l'impression pour l'imprimante.
StretchBlt | BOOL StretchBlt( HDC hdcDest, int nXOriginDest, int nYOriginDest, int nWidthDest, int nHeightDest, HDC hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc, DWORD dwRop ); | gdi32.dll | Cette fonction permet de copier un morceau dans une autre image.
StretchDIBits | int StretchDIBits(HDC hdc, int XDest, int YDest, int nDestWidth, int nDestHeight, int XSrc, int YSrc, int nSrcWidth, int nSrcHeight, CONST VOID *lpBits, CONST BITMAPINFO * lpBitsInfo, UINT iUsage, DWORD dwRop); | gdi32.dll | Cette fonction permet de copier les données de couleur d'un rectangle de pixels dans un DIB de la destination rectangulaire spécifié.
StrIsIntlEqualA | BOOL StrIsIntlEqualA(BOOL fCaseSens, LPCTSTR String1, LPCTSTR String2, INT nChar); | shlwapi.dll | Cette fonction permet de comparer le début de deux chaines de caractères ASCIZ.
StrNCatA | LPSTR StrNCatA(LPSTR lpszStr, LPCSTR lpszCat, INT cchMax); | shlwapi.dll | Cette fonction permet d'ajouter à la fin d'une chaine de caractères ASCIZ le contenu d'une chaine de caractères ASCIZ.
StrokeAndFillPath | BOOL StrokeAndFillPath(HDC hdc); | gdi32.dll | Cette fonction permet de fermer n'importe quel figures ouvertes dans un chemin, traitant les lignes en dehors du chemin en utilisant le crayon courant, et remplissant l'intérieur en utilisant la brosse.
StrokePath | BOOL StrokePath(HDC hdc); | gdi32.dll | Cette fonction permet d'effectuer un traitement d'un chemin spécifié en utilisant le crayon courant.
StrToIntExA | BOOL StrToIntExA( LPCSTR lpszStr, DWORD dwFlags, LPINT lpiRet); | shlwapi.dll | Cette fonction permet de convertir une chaine de caractères ASCIZ ayant un nombre décimal ou hexadécimal en une valeur numérique entière.
StrTrimA | BOOL StrTrimA( LPSTR lpszStr, LPCSTR lpszTrim); | shlwapi.dll | Cette fonction permet d'enlever les caractères spécifié au début et à la fin d'une chaine de caractères ASCIZ.
SubtractRect | BOOL SubtractRect(LPRECT lprcDest, CONST RECT * lprcSrc1, CONST RECT * lprcSrc2); | user32.dll | Cette fonction permet de demander les coordonnées d'un rectangle par soustraction d'un rectangle par un autre.
SuspendThread | DWORD SuspendThread(HANDLE hThread); | kernel32.dll | Cette fonction permet de suspendre le processus léger spécifié.
SwapBuffers | BOOL SwapBuffers(HDC hdc); | gdi32.dll | Cette fonction permet d'échanger le tampon de premier plan avec l'arrière-plan si le format du pixel courant pour la fenêtre est référencée par le périphérique de contexte spécifié inclus dans le tampon d'arrière plan.
SwapMouseButton | BOOL SwapMouseButton(BOOL fSwap); | user32.dll | Cette fonction permet d'échanger le comportement des boutons de gauche de la souris devienne celui de droite et que celui de droite devienne celui de gauche.
SwitchDesktop | BOOL SwitchDesktop(HDESK hDesk); | user32.dll | Cette fonction permet de fixer le bureau visible et actif.
SwitchToFiber | VOID SwitchToFiber(LPVOID lpFiber); | kernel32.dll | Cette fonction permet de planifier une fibre.
SwitchToThread | BOOL SwitchToThread(void); | kernel32.dll | Cette fonction permet de provoquer l'exécution d'un rendement de processus léger vers un autre processus léger étant prêt a être exécuté par le microprocesseur courant.
SystemParametersInfoA | BOOL SystemParametersInfoA(UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni); | user32.dll | Cette fonction permet de fixer certain paramètre du système d'exploitation Windows.
SystemParametersInfoW | BOOL SystemParametersInfoW(UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni); | user32.dll | Cette fonction permet de fixer certain paramètre du système d'exploitation Windows avec des paramètres de chaine de caractères étendues (Unicode).
SystemTimeToFileTime | BOOL SystemTimeToFileTime(CONST SYSTEMTIME * lpst, LPFILETIME lpft); | kernel32.dll | Cette fonction permet de convertir le temps système vers le temps fichier.
SystemTimeToTzSpecificLocalTime | BOOL SystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformation, LPSYSTEMTIME lpUniversalTime, LPSYSTEMTIME lpLocalTime); | kernel32.dll | Cette fonction permet de convertir l'UTC (Coordinated Universal Time) vers un fuseau horaire spécifié correspondant au temps locale.
TabbedTextOutA | LONG TabbedTextOutA(HDC hdc, int x, int y, LPCTSTR lpString, int nCount, int nTabPositions, LPINT lpnTabStopPositions, int nTabOrigin); | user32.dll | Cette fonction permet d'écrire une chaine de caractères à l'emplacement spécifié, en étendant la tabulation des valeurs spécifiés dans des positions d'une tableau de tabulation sans arrêt.
TabbedTextOutW | LONG TabbedTextOutW(HDC hdc, int x, int y, LPCWSTR lpString, int nCount, int nTabPositions, LPINT lpnTabStopPositions, int nTabOrigin); | user32.dll | Cette fonction permet d'écrire une chaine de caractères à l'emplacement spécifié, en étendant la tabulation des valeurs spécifiés dans des positions d'une tableau de tabulation sans arrêt avec des paramètres de chaine de caractères étendues (Unicode).
TerminateProcess | BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode); | kernel32.dll | Cette fonction permet de terminer le processus spécifié et tous ses processus légers.
TerminateThread | BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode); | kernel32.dll | Cette fonction permet de terminer le processus léger spécifié.
TextOutA | BOOL TextOutA(HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cbString); | gdi32.dll | Cette fonction permet d'afficher une chaine de caractères dans une fenêtre d'application.
TextOutW | BOOL TextOutW(HDC hdc, int nXStart, int nYStart, LPCWSTR lpString, int cbString); | gdi32.dll | Cette fonction permet d'afficher une chaine de caractères dans une fenêtre d'application avec des paramètres de chaine de caractères étendues (Unicode).
TileWindows | WORD WINAPI TileWindows( HWND hwndParent, WORD wFlags, LPCRECT lpRect, WORD cChildrenb, const HWND FAR *ahwndChildren ); | user32.dll | Cette fonction permet de présenter sous forme mosaïque les sous-fenêtre d'une fenêtre d'application MDI.
TlsAlloc | DWORD TlsAlloc(VOID); | kernel32.dll | Cette fonction permet d'allouer un processus léger d'entrepose d'index locale (TLS).
TlsFree | BOOL TlsFree(DWORD dwTlsIndex); | kernel32.dll | Cette fonction permet de libérer un processus léger d'entrepose d'index locale (TLS).
TlsGetValue | LPVOID TlsGetValue(DWORD dwTlsIndex); | kernel32.dll | Cette fonction permet de demander la valeur dans un appel de processus d'entreposage d'une fente locale (TLS) pour un index TLS spécifié.
TlsSetValue | BOOL TlsSetValue(DWORD dwTlsIndex, LPVOID lpvTlsValue); | kernel32.dll | Cette fonction permet d'entreposer la valeur d'un appel de processus d'entreposage d'une fente locale (TLS) pour un index TLS spécifié.
ToAscii | int ToAscii(UINT uVirtKey, UINT uScanCode, PBYTE lpbKeyState, LPWORD lpwTransKey, UINT fuState); | user32.dll | Cette fonction permet de traduire le code de touche virtuel spécifié et l'état du clavier en sa correspondance de caractère Windows ou de caractères.
ToAsciiEx | int ToAsciiEx(UINT uVirtKey, UINT ScanCode, BYTE *lpbKeyState, DWORD *lpdwTransKey, UINT fuState, HKL hkl); | user32.dll | Cette fonction permet de traduire le code de touche virtuel spécifié et l'état du clavier en sa correspondance de caractère Windows ou de caractères avec des options supplémentaires.
TouchFileTimes | BOOL TouchFileTimes( HANDLE FileHandle, PSYSTEMTIME pSystemTime); | imagehlp.dll | Cette fonction permet de fixer la date et l'heure d'un fichier avec un Handle.
ToUnicode | int ToUnicode(UINT wVirtKey, UINT wScanCode, PBYTE lpKeyState, LPWSTR pwszBuff, int cchBuff, UINT wFlags); | user32.dll | Cette fonction permet de traduire le code de touche virtuel spécifié et l'état du clavier en sa correspondance de caractère Unicode ou de caractères.
ToUnicodeEx | int ToUnicodeEx(UINT wVirtKey, UINT wScanCode, const BYTE *lpKeyState, LPWSTR pwszBuff, int cchBuff, UINT wFlags, HKL dwhkl); | user32.dll | Cette fonction permet de traduire le code de touche virtuel spécifié et l'état du clavier en sa correspondance de caractère Unicode ou de caractères avec des options supplémentaires.
TrackMouseEvent | BOOL TrackMouseEvent(LPTRACKMOUSEEVENT lpEventTrack); | user32.dll | Cette fonction permet de poster les messages lorsque le pointeur de souris laisse une fenêtre ou passe dessus une fenêtre pour un nombre de temps spécifié.
TrackPopupMenu | BOOL TrackPopupMenu( HMENU hMenu, UINT uFlags, int x, int y, int nReserved, HWND hWnd, CONST RECT * prcRect ); | user32.dll | Cette fonction permet d'afficher un menu contextuelle.
TrackPopupMenuEx | BOOL TrackPopupMenuEx(HMENU hmenu, UINT fuFlags, int x, int y, HWND hwnd, LPTPMPARAMS lptpm); | user32.dll | Cette fonction permet d'afficher un menu contextuel à l'emplacement spécifié et trace les items de sélection dans un menu contextuel.
TransactNamedPipe | BOOL TransactNamedPipe(HANDLE hNamedPipe, LPVOID lpvWriteBuf, DWORD cbWriteBuf, LPVOID lpvReadBuf, DWORD cbReadBuf, LPDWORD lpcbRead, LPOVERLAPPED lpo); | kernel32.dll | Cette fonction permet de combiner dans une simple opération réseau les fonctions d'écriture d'un message et de lecture de message d'un tuyau nommé spécifié.
TranslateAcceleratorA | int TranslateAcceleratorA(HWND hwnd, HACCEL haccl, LPMSG lpmsg); | user32.dll | Cette fonction permet d'effectuer le traitement des touches d'accélérations pour les commandes de menu.
TranslateAcceleratorW | int TranslateAcceleratorW(HWND hwnd, HACCEL haccl, LPMSG lpmsg); | user32.dll | Cette fonction permet d'effectuer le traitement des touches d'accélérations pour les commandes de menu avec des paramètres de chaine de caractères étendues (Unicode).
TranslateCharsetInfo | UINT TranslateCharsetInfo(DWORD FAR * lpSrc, LPCHARSETINFO lpCs, DWORD dwFlags); | user32.dll | Cette fonction permet d'effectuer une conversion, basé sur ensemble de caractères spécifié, un code de page ou une valeur de signature de police de caractères, ajustant tous les membres d'une structure de destination aux valeurs approprié.
TranslateMDISysAccel | BOOL TranslateMDISysAccel(HWND hwndClient, LPMSG lpmsg); | user32.dll | Cette fonction permet d'effectuer le traitement d'une combinaison de touches d'accélération pour une interface de document multiple (MDI) de la fenêtre enfant.
TranslateMessage | BOOL TranslateMessage(CONST MSG *lpmsg); | user32.dll | Cette fonction permet d'effectuer une transformation de message de touche virtuel en caractères de messages.
TransmitCommChar | BOOL TransmitCommChar(HANDLE hCommDev, char chTransmit); | kernel32.dll | Cette fonction permet de transmettre un caractère spécifié à venir dans des données en attente dans un tampon de sortie du périphérique de communication spécifié.
TryEnterCriticalSection | BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); | kernel32.dll | Cette fonction permet d'effectuer une tentative pour entrer dans une section critique sans blocage.
UnhandledExceptionFilter | LONG UnhandledExceptionFilter(STRUCT _EXCEPTION_POINTERS * lpexpExceptionInfo); | kernel32.dll | Cette fonction permet de passer une exception non définit au débogueur si le processus de déboguage est actif.
UnhookWindowsHook | BOOL UnhookWindowsHook(int nCode, HOOKPROC hkprc); | user32.dll | Cette fonction permet d'enlever la fonction crochet utilisateur pour une chaine de fonctions crochet.
UnhookWindowsHookEx | BOOL UnhookWindowsHookEx(HHOOK hhook); | user32.dll | Cette fonction permet d'enlever un crochet de fonction utilisateur d'une chaine de crochet.
UnionRect | BOOL UnionRect(LPRECT lprcDst, CONST RECT *lprcSrc1, CONST RECT *lprcSrc2); | user32.dll | Cette fonction permet d'effectuer la création de l'union de 2 rectangles.
UnloadKeyboardLayout | BOOL UnloadKeyboardLayout(HKL hkl); | user32.dll | Cette fonction permet d'enlever une schéma de clavier.
UnlockFile | BOOL UnlockFile(HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh, DWORD cbUnlockLow, DWORD cbUnlockHigh); | kernel32.dll | Cette fonction permet de déverrouiller une région d'un fichier ouvert.
UnlockFileEx | BOOL UnlockFileEx(HANDLE hFile, DWORD dwReserved, DWORD nNumberOfBytesToUnlockLow, DWORD nNumberOfBytesToUnlockHigh, LPOVERLAPPED lpOverlapped); | kernel32.dll | Cette fonction permet de déverrouiller une rangée d'octets précédemment verrouillé dans un fichier ouvert.
UnmapViewOfFile | BOOL UnmapViewOfFile(LPVOID lpBaseAddress); | kernel32.dll | Cette fonction permet de cartographier une vue cartographier dans un fichier d'un espace d'adressage d'un processus appelant.
UnpackDDElParam | BOOL UnpackDDElParam(UINT uMsg, LONG lParam, PUINT puLow, PUINT puHigh); | user32.dll | Cette fonction permet d'effectuer un décompactage d'une valeur DDE reçu spécifié vers un message DDE posté.
UnrealizeObject | BOOL UnrealizeObject(HGDIOBJ hgdiobj); | gdi32.dll | Cette fonction permet de réinitialiser l'origine de la brosse ou de réinitialiser la palette logique.
UnregisterClassA | BOOL UnregisterClassA(LPCTSTR lpszClass, HINSTANCE hinst); | user32.dll | Cette fonction permet d'enlever une classe de fenêtre et libère l'entreposage requis par la classe.
UnregisterClassW | BOOL UnregisterClassW(LPCWSTR lpszClass, HINSTANCE hinst); | user32.dll | Cette fonction permet d'enlever une classe de fenêtre et libère l'entreposage requis par la classe avec des paramètres de chaine de caractères étendues (Unicode).
UnregisterDeviceNotification | BOOL UnregisterDeviceNotification(HDEVNOTIFY Handle); | user32.dll | Cette fonction permet de désenregistrer un périphérique ou un type de périphérique pour que la fenêtre reçoivent des avertissements de changements.
UnregisterHotKey | BOOL UnregisterHotKey(HWND hwnd, int idHotKey); | user32.dll | Cette fonction permet de désenregistrer l'état des touches de combinaison (Alt, Ctrl, Shift) pour le processus léger.
UpdateColors | BOOL UpdateColors(HDC hdc); | gdi32.dll | Cette fonction permet de mettre-à-jour la région client d'un périphérique de contexte spécifié en faisant correspondre les couleurs courantes dans une région client, pixel par pixel, à une palette de système.
UpdateResourceA | BOOL UpdateResourceA(HANDLE hUpdateFile, LPCTSTR lpszType, LPCTSTR lpszName, WORD IDLanguage, LPVOID lpvData, DWORD cbData); | kernel32.dll | Cette fonction permet d'ajouter, supprimer ou remplacer une ressource dans un fichier exécutable.
UpdateResourceW | BOOL UpdateResourceW(HANDLE hUpdate, LPCWSTR lpType, LPCWSTR lpName, WORD wLanguage, LPVOID lpData, DWORD cbData); | kernel32.dll | Cette fonction permet d'ajouter, supprimer ou remplacer une ressource dans un fichier exécutable avec des paramètres de chaine de caractères étendues (Unicode).
UpdateWindow | BOOL UpdateWindow(HWND hwnd); | user32.dll | Cette fonction permet de mettre à jour la région client de la fenêtre donnée par l'envoie du message WM_PAINT à la fenêtre si la région à mettre à jour de la fenêtre n'est pas vide.
UrlCanonicalizeA | HRESULT UrlCanonicalizeA( LPCSTR pszUrl, LPSTR pszCanonicalized, LPDWORD pcchCanonicalized, DWORD dwFlags); | shlwapi.dll | Cette fonction permet de transformer un chemin d'un URL de sa forme absolue à relative, c'est-à-dire de remplacer des "../" pour atteinte le répertoire en court par exemple.
UrlCombineA | HRESULT UrlCombineA( LPCSTR pszBase, LPCSTR pszRelative, LPSTR pszCombined, LPDWORD pcchCombined, DWORD dwFlags); | shlwapi.dll | Cette fonction permet d'ajouter un chemin d'un URL à la fin d'un autre chemin d'URL.
UrlCreateFromPathA | HRESULT UrlCreateFromPathA( LPCTSTR pszPath, LPTSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved); | shlwapi.dll | Cette fonction permet de convertir un chemin de système d'exploitation de style DOS sous sa forme URL.
UrlEscapeA | HRESULT UrlEscapeA( LPCTSTR pszURL, LPTSTR pszEscaped, LPDWORD pcchEscaped, DWORD dwFlags ); | shlwapi.dll | Cette fonction permet de convertir les caractères particulier en format acceptable pour un URL.
UrlGetPartA | HRESULT UrlGetPartA( LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut, DWORD dwPart, DWORD dwFlags ); | shlwapi.dll | Cette fonction permet d'extraire une partie de l'URL.
UrlIsOpaqueA | BOOL UrlIsOpaqueA( LPCSTR pszUrl); | shlwapi.dll | Cette fonction permet de demander si le URL est opaque.
UrlUnescapeA | HRESULT UrlUnescapeA( LPSTR pszUrl, LPSTR pszUnescaped, LPDWORD pcchUnescaped, DWORD dwFlags ); | shlwapi.dll | Cette fonction permet de convertir les caractères particulier du URL en une chaine de caractères ASCIZ.
ValidateRect | BOOL ValidateRect(HWND hwnd, CONST RECT *lprc); | user32.dll | Cette fonction permet de valider une région client avec le rectangle donnée par suppression du rectangle de la région à mettre à jour de la fenêtre donnée.
ValidateRgn | BOOL ValidateRgn(HWND hwnd, HRGN hrgn); | user32.dll | Cette fonction permet de valider la région client avec la région donnée par suppression de la région de la mise à jour courante d'une région de fenêtre spécifié.
VerifyScreenSavePwd | BOOL VerifyScreenSavePwd(HWND hwnd); | password.cpl | Cette fonction permet d'effectuer la vérification du mots de passe lors du lancement d'un écran de veille.
VerLanguageNameA | DWORD VerLanguageNameA( DWORD idLang,LPTSTR lpszLang, DWORD cbLang); | kernel32.dll | Cette fonction permet de demander le MID identifiant le langage utilisé par le système d'exploitation.
VerLanguageNameW | DWORD VerLanguageNameW(DWORD wLang, LPWSTR szLang, DWORD nSize); | kernel32.dll | Cette fonction permet de demander le MID identifiant le langage utilisé par le système d'exploitation avec des paramètres de chaines de caractères étendues (Unicode).
VirtualAlloc | LPVOID VirtualAlloc(LPVOID lpvAddress, DWORD cbSize, DWORD fdwAllocationType, DWORD fdwProtect); | kernel32.dll | Cette fonction permet de réservé ou d'engager une région de pages dans un espace d'adressage virtuel d'un processus appelant.
VirtualAllocEx | LPVOID VirtualAllocEx(HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect); | kernel32.dll | Cette fonction permet de réservé ou d'engager une région de pages dans un espace d'adressage virtuel pour le processus spécifié.
VirtualFree | BOOL VirtualFree(LPVOID lpvAddress, DWORD cbSize, DWORD fdwFreeType); | kernel32.dll | Cette fonction permet de relâcher ou désengager (ou les deux) une région de pages dans une espace de d'adressage virtuel d'un processus appelant.
VirtualFreeEx | BOOL VirtualFreeEx(HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType); | kernel32.dll | Cette fonction permet de relâcher ou désengager (ou les deux) une région de pages dans une espace de d'adressage virtuel pour un processus spécifié.
VirtualLock | BOOL VirtualLock(LPVOID lpvAddress, DWORD cbSize); | kernel32.dll | Cette fonction permet de verrouiller une région spécifié dans un espace d'adressage virtuel de la mémoire en s'assurant que l'accès ultérieur à la région ne soit pas passible d'une erreur de page.
VirtualProtect | BOOL VirtualProtect(LPVOID lpvAddress, DWORD cbSize, DWORD fdwNewProtect, PDWORD pfdwOldProtect); | kernel32.dll | Cette fonction permet de changer la protection d'accès d'une région de pages engagé dans un espace d'adressage virtuel d'un processus appelant.
VirtualProtectEx | BOOL VirtualProtectEx(HANDLE hProcess, LPVOID lpvAddress, DWORD cbSize, DWORD fdwNewProtect, PDWORD pfdwOldProtect); | kernel32.dll | Cette fonction permet de changer la protection d'accès d'une région de pages engagé dans un espace d'adressage virtuel d'un processus spécifié.
VirtualQuery | DWORD VirtualQuery(LPCVOID lpvAddress, PMEMORY_BASIC_INFORMATION pmbiBuffer, DWORD cbLength); | kernel32.dll | Cette fonction permet de fournir les informations à propos d'un rang de pages dans un espace d'adressage virtuel pour un processus appelant.
VirtualQueryEx | DWORD VirtualQueryEx(HANDLE hProcess, LPCVOID lpvAddress, PMEMORY_BASIC_INFORMATION pmbiBuffer, DWORD cbLength); | kernel32.dll | Cette fonction permet de fournir les informations à propos d'un rang de pages dans un espace d'adressage virtuel dans un processus spécifié.
VirtualUnlock | BOOL VirtualUnlock(LPVOID lpvAddress, DWORD cbSize); | kernel32.dll | Cette fonction permet de déverrouiller un range de pages spécifié dans un espace d'adressage virtuel d'un processus et activant le système pour l'échange de pages dans le fichier de pagination si nécessaire.
VkKeyScanA | SHORT VkKeyScanA(TCHAR ch); | user32.dll | Cette fonction permet d'effectuer la conversion de caractère correspondant un code de touche virtuel et de l'état de touche de combinaison du clavier courant.
VkKeyScanExA | SHORT VkKeyScanExA(UINT uChar, HKL hkl); | user32.dll | Cette fonction permet d'effectuer la conversion de caractère correspondant un code de touche virtuel et de l'état de touche de combinaison du clavier courant en utilisant le langage et la couche physique du clavier.
VkKeyScanExW | SHORT VkKeyScanExW(WCHAR uChar, HKL hkl); | user32.dll | Cette fonction permet d'effectuer la conversion de caractère correspondant un code de touche virtuel et de l'état de touche de combinaison du clavier courant en utilisant le langage et la couche physique du clavier avec des paramètres de chaine de caractères étendues (Unicode).
VkKeyScanW | SHORT VkKeyScanW(WCHAR ch); | user32.dll | Cette fonction permet d'effectuer la conversion de caractère correspondant un code de touche virtuel et de l'état de touche de combinaison du clavier courant avec des paramètres de chaine de caractères étendues (Unicode).
WaitCommEvent | BOOL WaitCommEvent(HANDLE hCommDev, LPDWORD lpfdwEvtMask, LPOVERLAPPED lpo); | kernel32.dll | Cette fonction permet d'attendre qu'un événement se produise sur le périphérique de communication spécifié.
WaitForDebugEvent | BOOL WaitForDebugEvent(LPDEBUG_EVENT lpde, DWORD dwTimeout); | kernel32.dll | Cette fonction permet d'attendre qu'un événement de déboguage se produise dans un processus en déboguage.
WaitForInputIdle | DWORD WaitForInputIdle(HANDLE hProcess, DWORD dwTimeout); | user32.dll | Cette fonction permet d'attendre tant que le processus données est en attente d'une entrée utilisateur sans attendre d'entrée ou de dépassement d'intervalle de temps.
WaitForMultipleObjects | DWORD WaitForMultipleObjects(DWORD cObjects, CONST HANDLE * lphObjects, BOOL fWaitAll, DWORD dwTimeout); | kernel32.dll | Cette fonction permet de retourner lorsque l'un des objets indiqués est dans l'état signalé, ou lorsque le délai d'attente spécifié s'est écoulé.
WaitForMultipleObjectsEx | DWORD WaitForMultipleObjectsEx(DWORD cObjects, CONST HANDLE * lphObjects, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable); | kernel32.dll | Cette fonction permet d'attendre que la fonction soit utilisés pour un traitement d'attendre d'alerte.
WaitForSingleObject | DWORD WaitForSingleObject( HANDLE hObject, DWORD dwTimeout ); | kernel32.dll | Cette fonction permet d'attendre qu'un objet retourne un signal ou que le délai spécifié soit écoulé.
WaitForSingleObjectEx | DWORD WaitForSingleObjectEx(HANDLE hObject, DWORD dwTimeout, BOOL fAlertable); | kernel32.dll | Cette fonction permet d'étendre la fonction d'attente étant utilisés pour un traitement d'attendre d'alerte.
WaitMessage | BOOL WaitMessage(VOID); | user32.dll | Cette fonction permet de rendre le contrôle à une autre application quand une application n'a pas une autre tâche en traitement.
WaitNamedPipeA | BOOL WaitNamedPipeA(LPCTSTR lpszPipeName, DWORD dwTimeout); | kernel32.dll | Cette fonction permet d'attendre jusqu'à ce qu'un dépassement d'intervalle ou d'une instance d'un tuyau nommé spécifié soit disponible pour être connecté à processus en attente d'une opération ConnectNamedPipe se produise.
WaitNamedPipeW | BOOL WaitNamedPipeW(LPCWSTR name, DWORD nTimeOut); | kernel32.dll | Cette fonction permet d'attendre jusqu'à ce qu'un dépassement d'intervalle ou d'une instance d'un tuyau nommé spécifié soit disponible pour être connecté à processus en attente d'une opération ConnectNamedPipe se produise avec des paramètres de chaine de caractères étendue (Unicode).
waveOutGetNumDevs | UINT waveOutGetNumDevs(VOID); | winmm.dll | Cette fonction permet de demander le nombre de sortie Wave disponible sur le système d'explotation Windows.
WideCharToMultiByte | int WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cchMultiByte, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar); | kernel32.dll | Cette fonction permet de cartographier une chaine de caractères Unicode en une nouvelle chaine de caractères.
WidenPath | BOOL WidenPath(HDC hdc); | gdi32.dll | Cette fonction permet de redéfinir le chemin courant de la région devant être repeinte si le chemin est un trait utilisant le crayon courant sélectionné dans le périphérique de contexte spécifié.
WindowFromDC | HWND WindowFromDC(HDC hdc); | user32.dll | Cette fonction permet de demander le Handle de la fenêtre correspondante au périphérique de contexte spécifié.
WindowFromPoint | HWND WindowFromPoint(POINT pt); | user32.dll | Cette fonction permet de demander le Handle associé à une fenêtre correspondant au coordonnées pixel spécifié.
WinExec | UINT WinExec( LPCSTR lpszCmdLine, UINT fuCmdShow ); | kernel32.dll | Cette fonction permet de lancer l'exécution d'un programme externe.
WinHelpA | BOOL WinHelpA(HWND hwnd, LPCTSTR lpszHelp, UINT uCommand, DWORD dwData); | user32.dll | Cette fonction permet de démarrer la fenêtre d'aide (WINHELP.EXE) et passe les données optionnels indiquant la nature de la requête d'aide pour l'application.
WinHelpW | BOOL WinHelpW(HWND hwnd, LPCTSTR lpszHelp, UINT uCommand, DWORD dwData); | user32.dll | Cette fonction permet de démarrer la fenêtre d'aide (WINHELP.EXE) et passe les données optionnels indiquant la nature de la requête d'aide pour l'application avec des paramètres de chaine de caractères étendues (Unicode).
WNetAddConnection2A | DWORD WNetAddConnection2A(LPNETRESOURCE lpNetResource, LPCTSTR lpPassword, LPCTSTR lpUsername, DWORD fdwConnection); | mpr.dll | Cette fonction permet de fabriquer une connexion vers une ressource réseau.
WNetAddConnection2W | DWORD WNetAddConnection2W(LPNETRESOURCEW lpNetResource, LPCWSTR lpPassword, LPCWSTR lpUsername, DWORD fdwConnection); | mpr.dll | Cette fonction permet de fabriquer une connexion vers une ressource réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetAddConnection3A | DWORD WNetAddConnection3A(HWND hwndOwner, LPNETRESOURCE lpNetResource, LPTSTR lpPassword, LPTSTR lpUserName, DWORD fdwConnection); | mpr.dll | Cette fonction permet de fabriquer une connexion vers une ressource réseau en utilisant des options supplémentaires.
WNetAddConnection3W | DWORD WNetAddConnection3W(HWND hwndOwner, LPNETRESOURCEW lpNetResource, LPCWSTR lpPassword, LPCWSTR lpUserName, DWORD fdwConnection); | mpr.dll | Cette fonction permet de fabriquer une connexion vers une ressource réseau en utilisant des options supplémentaires avec des paramètres de chaine de caractères étendues (Unicode).
WNetAddConnectionA | DWORD WNetAddConnectionA(LPTSTR lpszRemoteName, LPTSTR lpszPassword,LPTSTR lpszLocalName); | mpr.dll | Cette fonction permet d'effectuer la connexion d'une unité de disque à une ressource réseau.
WNetAddConnectionW | DWORD WNetAddConnectionW(LPCWSTR lpszRemoteName, LPCWSTR lpszPassword, LPCWSTR lpszLocalName); | mpr.dll | Cette fonction permet d'effectuer la connexion d'une unité de disque à une ressource réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetCancelConnection2A | DWORD WNetCancelConnection2A(LPTSTR lpszName, DWORD fdwConnection, BOOL fForce); | mpr.dll | Cette fonction permet d'effectuer l'arrêt d'une connexion réseau existante.
WNetCancelConnection2W | DWORD WNetCancelConnection2W(LPCWSTR lpszName, DWORD fdwConnection, BOOL fForce); | mpr.dll | Cette fonction permet d'effectuer l'arrêt d'une connexion réseau existante avec des paramètres de chaine de caractères étendues (Unicode).
WNetCancelConnectionA | DWORD WNetCancelConnectionA(LPTSTR lpszName, BOOL fForce); | mpr.dll | Cette fonction permet d'effectuer la déconnexion d'une unité de disque à une ressource réseau.
WNetCancelConnectionW | DWORD WNetCancelConnectionW(LPCWSTR lpszName, BOOL fForce); | mpr.dll | Cette fonction permet d'effectuer la déconnexion d'une unité de disque à une ressource réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetCloseEnum | DWORD WNetCloseEnum(HANDLE hEnum); | mpr.dll | Cette fonction permet de terminer l'énumération d'une ressource réseau débuté par la fonction WNetOpenEnum.
WNetConnectionDialog | DWORD WNetConnectionDialog( HWND hwnd, DWORD fdwResourceType ); | mpr.dll | Cette fonction permet d'afficher une boite de dialogue permettant à une unité de disque de se connecter à une ressource réseau.
WNetConnectionDialog1A | DWORD WNetConnectionDialog1A(LPCONNECTDLGSTRUCT lpConnDlgStruct); | mpr.dll | Cette fonction permet d'introduire une boite de dialogue de navigation général pour la connexion des ressources de réseau.
WNetConnectionDialog1W | DWORD WNetConnectionDialog1W(LPCONNECTDLGSTRUCTW lpConnDlgStruct); | mpr.dll | Cette fonction permet d'introduire une boite de dialogue de navigation général pour la connexion des ressources de réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetDisconnectDialog | DWORD WNetDisconnectDialog( HWND hwnd, DWORD fdwResourceType ); | mpr.dll | Cette fonction permet d'afficher une boite de dialogue permettant à une unité de disque de se déconnecter à une ressource réseau.
WNetDisconnectDialog1A | DWORD WNetDisconnectDialog1A(LPDISCDLGSTRUCT lpConnDlgStruct); | mpr.dll | Cette fonction permet d'effectuer la déconnexion d'une ressource de réseau.
WNetDisconnectDialog1W | DWORD WNetDisconnectDialog1W(LPDISCDLGSTRUCTW lpConnDlgStruct); | mpr.dll | Cette fonction permet d'effectuer la déconnexion d'une ressource de réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetEnumResourceA | DWORD WNetEnumResourceA(HANDLE hEnum, LPDWORD lpcEntries, LPVOID lpvBuffer, LPDWORD lpcbBuffer); | mpr.dll | Cette fonction permet de continuer l'énumération de ressource réseau commencé par la fonction WNetOpenEnum.
WNetEnumResourceW | DWORD WNetEnumResourceW(HANDLE hEnum, LPDWORD lpcEntries, LPVOID lpvBuffer, LPDWORD lpcbBuffer); | mpr.dll | Cette fonction permet de continuer l'énumération de ressource réseau commencé par la fonction WNetOpenEnum avec des paramètres de chaine de caractères étendues (Unicode).
WNetGetConnectionA | DWORD WNetGetConnectionA(LPTSTR lpszLocalName, LPTSTR lpszRemoteName, LPDWORD lpcchBuffer); | mpr.dll | Cette fonction permet de demander le nom de la ressource réseau associé avec un périphérique local.
WNetGetConnectionW | DWORD WNetGetConnectionW(LPCWSTR lpszLocalName, LPWSTR lpszRemoteName, LPDWORD lpcchBuffer); | mpr.dll | Cette fonction permet de demander le nom de la ressource réseau associé avec un périphérique local avec des paramètres de chaine de caractères étendues (Unicode).
WNetGetLastErrorA | DWORD WNetGetLastErrorA(LPDWORD lpdwErrorCode, LPTSTR lpszDescription, DWORD cchDescription, LPTSTR lpszName, DWORD cchName); | mpr.dll | Cette fonction permet de demander le plus récent des derniers code d'erreur étendue des fonctions réseau du système d'exploitation.
WNetGetLastErrorW | DWORD WNetGetLastErrorW(LPDWORD lpdwErrorCode, LPWSTR lpszDescription, DWORD cchDescription, LPWSTR lpszName, DWORD cchName); | mpr.dll | Cette fonction permet de demander le plus récent des derniers code d'erreur étendue des fonctions réseau du système d'exploitation avec des paramètres de chaine de caractères étendues (Unicode).
WNetGetNetworkInformationA | DWORD WNetGetNetworkInformationA(LPCTSTR lpProvider, LPNETINFOSTRUCT lpNetInfoStruct); | mpr.dll | Cette fonction permet de demander les informations étendues à propos d'un fournisseur de réseau spécifié dont le nom a été retourné lors d'une énumération réseau précédente.
WNetGetNetworkInformationW | DWORD WNetGetNetworkInformationW(LPCWSTR lpProvider, LPNETINFOSTRUCT lpNetInfoStruct); | mpr.dll | Cette fonction permet de demander les informations étendues à propos d'un fournisseur de réseau spécifié dont le nom a été retourné lors d'une énumération réseau précédente avec des paramètres de chaine de caractères étendues (Unicode).
WNetGetProviderNameA | DWORD WNetGetProviderNameA(DWORD dwNetType, LPTSTR lpProviderName, LPDWORD lpBufferSize); | mpr.dll | Cette fonction permet de demander le nom de fournisseur pour un type spécifié dans un réseau.
WNetGetProviderNameW | DWORD WNetGetProviderNameW(DWORD dwNetType, LPWSTR lpProviderName, LPDWORD lpBufferSize); | mpr.dll | Cette fonction permet de demander le nom de fournisseur pour un type spécifié dans un réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetGetResourceParentA | DWORD WNetGetResourceParentA(LPNETRESOURCE lpNetResource, LPVOID lpBuffer, LPDWORD lpBufferSize); | mpr.dll | Cette fonction permet de naviguer à partir d'une ressource.
WNetGetResourceParentW | DWORD WNetGetResourceParentW(LPNETRESOURCEW lpNetResource, LPVOID lpBuffer, LPDWORD lpBufferSize); | mpr.dll | Cette fonction permet de naviguer à partir d'une ressource avec des paramètres de chaine de caractères étendues (Unicode).
WNetGetUniversalNameA | DWORD WNetGetUniversalNameA(LPCTSTR lpLocalPath, DWORD dwInfoLevel, LPVOID lpBuffer, LPDWORD lpBufferSize); | mpr.dll | Cette fonction permet de prendre un chemin, avec comme base une unité de disque, pour un réseau de ressource et demande la structure de données contenant des formes universel de ce nom.
WNetGetUniversalNameW | DWORD WNetGetUniversalNameW(LPCWSTR lpLocalPath, DWORD dwInfoLevel, LPVOID lpBuffer, LPDWORD lpBufferSize); | mpr.dll | Cette fonction permet de prendre un chemin, avec comme base une unité de disque, pour un réseau de ressource et demande la structure de données contenant des formes universel de ce nom avec des paramètres de chaine de caractères étendues (Unicode).
WNetGetUserA | DWORD WNetGetUserA(LPTSTR lpszLocalName,LPTSTR lpszUserName,LPDWORD lpcchBuffer); | mpr.dll | Cette fonction permet de connaitre le nom de l'utilisateur utilisé pour la connexion réseau.
WNetGetUserW | DWORD WNetGetUserW(LPCWSTR lpszLocalName, LPWSTR lpszUserName,LPDWORD lpcchBuffer); | mpr.dll | Cette fonction permet de connaitre le nom de l'utilisateur utilisé pour la connexion réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetOpenEnumA | DWORD WNetOpenEnumA(DWORD fdwScope, DWORD fdwType, DWORD fdwUsage, LPNETRESOURCE lpNetResource, LPHANDLE lphEnum); | mpr.dll | Cette fonction permet de démarrer l'énumération de ressource réseau ou de connexions existantes.
WNetOpenEnumW | DWORD WNetOpenEnumW(DWORD fdwScope, DWORD fdwType, DWORD fdwUsage, LPNETRESOURCEW lpNetResource, LPHANDLE lphEnum); | mpr.dll | Cette fonction permet de démarrer l'énumération de ressource réseau ou de connexions existantes avec des paramètres de chaine de caractères étendues (Unicode).
WNetSetConnectionA | DWORD WNetSetConnectionA(LPCTSTR lpName, DWORD dwProperty, LPVOID pvValue); | mpr.dll | Cette fonction permet de modifier les caractéristiques d'une connexion réseau.
WNetSetConnectionW | DWORD WNetSetConnectionW(LPCWSTR lpName, DWORD dwProperty, LPVOID pvValue); | mpr.dll | Cette fonction permet de modifier les caractéristiques d'une connexion réseau avec des paramètres de chaine de caractères étendues (Unicode).
WNetUseConnectionA | DWORD WNetUseConnectionA(HWND hwndOwner, LPNETRESOURCE lpNetResource, LPCTSTR lpPassword, LPCTSTR lpUserID, DWORD dwFlags, LPTSTR lpAccessName, LPDWORD lpBufferSize, LPDWORD lpResult); | mpr.dll | Cette fonction permet de fabriquer une connexion vers une ressource réseau.
WNetUseConnectionW | DWORD WNetUseConnectionW(HWND hwndOwner, LPNETRESOURCEW lpNetResource, LPCWSTR lpPassword, LPCWSTR lpUserID, DWORD dwFlags, LPWSTR lpAccessName, LPDWORD lpBufferSize, LPDWORD lpResult); | mpr.dll | Cette fonction permet de fabriquer une connexion vers une ressource réseau avec des paramètres de chaine de caractères étendues (Unicode).
WriteConsoleA | BOOL WriteConsoleA(HANDLE hConsoleOutput, CONST VOID *lpvBuffer, DWORD cchToWrite, LPDWORD lpcchWritten, LPVOID lpvReserved); | kernel32.dll | Cette fonction permet d'effectuer l'écriture d'une chaine de caractères dans la sortie de la console standard.
WriteConsoleW | BOOL WriteConsoleW(HANDLE hConsoleOutput, CONST VOID *lpvBuffer, DWORD cchToWrite, LPDWORD lpcchWritten, LPVOID lpvReserved); | kernel32.dll | Cette fonction permet d'effectuer l'écriture d'une chaine de caractères dans la sortie de la console standard avec des paramètres de chaine de caractères étendues (Unicode).
WriteConsoleInputA | BOOL WriteConsoleInputA(HANDLE hConsoleInput, PINPUT_RECORD pirBuffer, DWORD cInRecords, LPDWORD lpcWritten); | kernel32.dll | Cette fonction permet d'écrire des données directement dans l'entrée du tampon de la console.
WriteConsoleInputW | BOOL WriteConsoleInputW(HANDLE hConsoleInput, CONST INPUT_RECORD *lpBuffer, DWORD nLength, LPDWORD lpNumberOfEventsWritten); | kernel32.dll | Cette fonction permet d'écrire des données directement dans l'entrée du tampon de la console avec des paramètres de chaine de caractères étendues (Unicode).
WriteConsoleOutputA | BOOL WriteConsoleOutputA(HANDLE hConsoleOutput, PCHAR_INFO pchiSrcBuffer, COORD coordSrcBufferSize, COORD coordSrcBufferCoord, PSMALL_RECT psrctDestRect); | kernel32.dll | Cette fonction permet d'écrire le caractère et la donnée d'attribut de couleur vers un bloc rectangulaire spécifié dans une cellule de caractères d'un tampon de console d'écran.
WriteConsoleOutputW | BOOL WriteConsoleOutputW(HANDLE hConsoleOutput, PCHAR_INFO pchiSrcBuffer, COORD coordSrcBufferSize, COORD coordSrcBufferCoord, PSMALL_RECT psrctDestRect); | kernel32.dll | Cette fonction permet d'écrire le caractère et la donnée d'attribut de couleur vers un bloc rectangulaire spécifié dans une cellule de caractères d'un tampon de console d'écran avec des paramètres de chaine de caractères étendue (Unicode).
WriteConsoleOutputAttribute | BOOL WriteConsoleOutputAttribute(HANDLE hConsoleOutput, LPWORD lpwAttribute, DWORD cWriteCells, COORD coordWrite, LPDWORD lpcWritten); | kernel32.dll | Cette fonction permet de copier un nombre d'attributs de couleurs de premier plan et d'arrière-plan dans une cellule consécutive d'un tampon de console d'écran en débutant à l'emplacement spécifié.
WriteConsoleOutputCharacterA | BOOL WriteConsoleOutputCharacterA(HANDLE hConsoleOutput, LPTSTR lpWriteBuffer, DWORD cchWrite, COORD coordWriteCoord, LPDWORD lpcWritten); | kernel32.dll | Cette fonction permet d'effectuer une copie d'un nombre de caractères à une cellule consécutive d'un tampon de console d'écran en débutant à l'emplacement spécifié.
WriteConsoleOutputCharacterW | BOOL WriteConsoleOutputCharacterW(HANDLE hConsoleOutput, LPCWSTR str, DWORD length, COORD coord, LPDWORD lpNumCharsWritten); | kernel32.dll | Cette fonction permet d'effectuer une copie d'un nombre de caractères à une cellule consécutive d'un tampon de console d'écran en débutant à l'emplacement spécifié avec des paramètres de chaine de caractères étendue (Unicode).
WriteFile | BOOL WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped); | kernel32.dll | Cette fonction permet d'écrire des données dans un fichier et est conçue pour des opérations sychronisé ou désychronisé.
WriteFileEx | BOOL WriteFileEx(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPOVERLAPPED lpOverlapped,LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); | kernel32.dll | Cette fonction permet d'écrire des données dans un fichier.
WritePrivateProfileSectionA | BOOL WritePrivateProfileSectionA(LPCTSTR lpszSection, LPCTSTR lpszKeysAndValues, LPCTSTR lpszFile); | kernel32.dll | Cette fonction permet de remplacer les clefs et les valeurs dans la section spécifié d'un fichier d'initialisation.
WritePrivateProfileSectionW | BOOL WritePrivateProfileSectionW(LPCWSTR section, LPCWSTR string, LPCWSTR filename); | kernel32.dll | Cette fonction permet de remplacer les clefs et les valeurs dans la section spécifié d'un fichier d'initialisation avec des paramètres de chaine de caractères étendue (Unicode).
WritePrivateProfileStringA | BOOL WritePrivateProfileStringA(LPCTSTR lpszSection, LPCTSTR lpszKey, LPCTSTR lpszString, LPCTSTR lpszFile); | kernel32.dll | Cette fonction permet de copier une chaine de caractères dans une section spécifié d'un fichier d'initialisation spécifié.
WritePrivateProfileStringW | BOOL WritePrivateProfileStringW(LPCWSTR lpszSection, LPCWSTR lpszKey, LPCWSTR lpszString, LPCWSTR lpszFile); | kernel32.dll | Cette fonction permet de copier une chaine de caractères dans une section spécifié d'un fichier d'initialisation spécifié avec des paramètres de chaine de caractères étendue (Unicode).
WriteProcessMemory | BOOL WriteProcessMemory(HANDLE hProcess, LPVOID lpBaseAddress, LPVOID lpBuffer, DWORD cbWrite, LPDWORD lpNumberOfBytesWritten); | kernel32.dll | Cette fonction permet d'écrire en mémoire pour le processus spécifié.
WriteProfileSectionA | BOOL WriteProfileSectionA(LPCTSTR lpszSection, LPCTSTR lpszKeysAndValues); | kernel32.dll | Cette fonction permet de remplacer le contenu de la section spécifié dans le fichier «WIN.INI» avec les clefs et les valeurs spécifiés.
WriteProfileSectionW | BOOL WriteProfileSectionW(LPCWSTR lpszSection, LPCWSTR lpszKeysAndValues); | kernel32.dll | Cette fonction permet de remplacer le contenu de la section spécifié dans le fichier «WIN.INI» avec les clefs et les valeurs spécifiés et avec les paramètres de chaine de caractères étendues (Unicode).
WriteProfileStringA | BOOL WriteProfileStringA(LPCTSTR lpszSection, LPCTSTR lpszKey, LPCTSTR lpszString); | kernel32.dll | Cette fonction permet de copier une chaine de caractères dans la section spécifié d'un fichier «WIN.INI».
WriteProfileStringW | BOOL WriteProfileStringW(LPCWSTR section, LPCWSTR entry, LPCWSTR string); | kernel32.dll | Cette fonction permet de copier une chaine de caractères dans la section spécifié d'un fichier «WIN.INI» avec des paramètres de chaine de caractères étendues (Unicode).
WriteTapemark | DWORD WriteTapemark(HANDLE hDevice, DWORD dwTapemarkType, DWORD dwTapemarkCount, BOOL bImmediate); | kernel32.dll | Cette fonction permet d'écrire une marque de fichier, un fixeur de marque, une marque de fichier court ou une long marque de fichier dans un périphérique de bande magnétique.
wsprintfA | int wsprintfA(LPTSTR lpOut, LPCTSTR lpFmt, ...); | user32.dll | Cette fonction permet d'écrire des données formatté d'une chaine de caractères vers un tampon.
wsprintfW | int wsprintfW(LPWSTR lpOut, LPCWSTR lpFmt, ...); | user32.dll | Cette fonction permet d'écrire des données formatté d'une chaine de caractères vers un tampon avec des paramètres de chaine de caractères étendues (Unicode).
wvsprintfA | int wvsprintfA(LPTSTR lpOutput, LPCTSTR lpFormat, va_list arglist); | user32.dll | Cette fonction permet de formatter et d'entreposer une série de caractères et de valeurs virtuel dans un tampon.
wvsprintfW | int wvsprintfW(LPWSTR lpOutput, LPCWSTR lpFormat, va_list arglist); | user32.dll | Cette fonction permet de formatter et d'entreposer une série de caractères et de valeurs virtuel dans un tampon avec des paramètres de chaine de caractères étendues (Unicode).
_hread | long _hread(HFILE hFile, LPVOID lpBuffer, long lBytes); | kernel32.dll | Cette fonction permet d'effectuer la lecture de données dans le fichier spécifié.
_hwrite | long _hwrite(HFILE hFile, LPCSTR lpBuffer, long lBytes); | kernel32.dll | Cette fonction permet d'effectuer l'écriture de données dans le fichier spécifié.
_lclose | HFILE _lclose(HFILE hFile); | kernel32.dll | Cette fonction permet de fermer le fichier Handle spécifié.
_lcreat | HFILE _lcreat(LPCSTR lpPathName, int iAttribute); | kernel32.dll | Cette fonction permet de créer ou ouvrir un fichier spécifié.
_llseek | LONG _llseek(HFILE hFile, LONG lOffset, int iOrigin ); | kernel32.dll | Cette fonction permet de positionner le pointeur du fichier Handle spécifié.
_lopen | HFILE _lopen(LPCSTR lpPathName, int iReadWrite); | kernel32.dll | Cette fonction permet d'ouvrir un fichier existant et de fixer le pointeur de fichier au début du fichier.
_lread | UINT _lread(HFILE hFile, LPVOID lpBuffer, UINT uBytes); | kernel32.dll | Cette fonction permet d'effectuer la lecture de données dans un fichier spécifié.
_lwrite | UINT _lwrite(HFILE hFile, LPCSTR lpBuffer, UINT uBytes); | kernel32.dll | Cette fonction permet d'effectuer l'écriture de données du fichier spécifié.

Vu dans http://www.gladir.com

Articles - Les géants de l'informatique - Microsoft

Dernière mise à jour: Dimanche, le 27 septembre 2009


Sommaire.html

Sommaire Vu dans ... http://chgi.developpez.com/windows/winmain/

---------------------------------------------------------------- Les bases :
01- La fenêtre principale.
02- Contrôles et messages.
03- Les commandes et notifications.
04- Les boîtes de dialogue communes.
05- Les contextes de périphériques.
06- Dessiner.
07- Les ressources.
08- Les boîtes de dialogue personnalisées.
09- Communiquer avec les boîtes de dialogue.
10- Les accélérateurs clavier.
11- Une boîte de dialogue comme fenêtre principale.
12- Les contrôles supplémentaires de Windows.
13- Donner le style XP à vos applications.
14- Sous-classement de fenêtres.
15- Afficher une image.
---------------------------------------------------------------- Les boîtes de dialogue :
16- la boîte de dialogue d'ouverture de fichier. (Chap. 4).
17- la boîte de dialogue de sauvegarde.
18- la boîte de dialogue de choix de polices de caractères.
19- la boîte de dialogue de choix de couleurs.
---------------------------------------------------------------- Les contrôles :
20- Le contrôle d'édition. (Chap. 9).
21- Le contrôle CheckBox ou case à cocher.
22- Le contrôle Radiobutton ou bouton radio.
23- Le contrôle ListBox ou liste de choix.
24- Le contrôle ComboBox ou liste déroulante.
25- Le contrôle Scroll bar ou ascenseur.
26- Le contrôle Static.
27- Le contrôle Progress Bar ou Barre de progression. (Chap. 12).
28- Le contrôle Status Bar ou Barre d'état.
29- Le contrôle TrackBar ou Slider.
30- Le contrôle List View.
31- Le contrôle Tree View.
32- Le contrôle Tab ou boîte à onglets.
33- Le contrôle Toolbar.
34- Le contrôle ToolTips.
35- Le contrôle PropertySheet.
---------------------------------------------------------------- Divers :
36- Les Timers.
37- Les menus contextuels.
38- Les boîtes de dialogues non modales.
39- Path and clip.
40- Le Tray Icon.
41- numération de ressources.
42- Afficher une image sous Windows NT.
43- Les services NT.
44- Une seule instance pour l'application.
---------------------------------------------------------------- Exemples complet en pure API Win32:
45- Un démineur.
46- Un mastermind.


01 - La fenêtre principale_.htm

I. Introduction :▲

Windows est un système d'exploitation proposant une interface graphique. Dans ce premier article, nous allons créer une application composée seulement d'une fenêtre principale, dont le but et de montrer l'architecture d'un programme Windows.

Image non disponible

Prenez garde au terme de fenêtre, dans Windows. Il signifie objet visuel, donc un bouton est une fenêtre ! Celle apparaissant au démarrage de l'application est souvent appelée fenêtre principale. Nous emploierons tout de même le mot fenêtre pour désigner une fenêtre du type de la fenêtre principale et le mot contrôle pour désigner les objets visuels qu'elles peuvent contenir...

II. La fonction WinMain :▲

Le point d'entrée d'une application Windows est la fonction WinMain. C'est l'équivalent de la fonction main des applications classiques. Elle est appelée par le système d'exploitation au lancement du programme. Il lui fournit 4 paramètres.

Sélectionnez

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow);

  • Le premier paramètre est le handle d'instance de l'application. C'est un numéro unique attribué par le système d'exploitation qui lui permet de l'identifier.
  • Le second paramètre est toujours NULL pour les applications Win32.
  • Le troisième paramètre est un pointeur sur la ligne de commande.
  • Et enfin le quatrième et dernier paramètre est un identificateur qui indique comment Windows désire afficher la fenêtre (normal, caché, minimisé ...).

C'est donc dans cette fonction (WinMain) que nous allons créer la fenêtre principale.

III. Création de la fenêtre :▲

La fonction qui permet de créer une fenêtre se nomme CreateWindow :

Sélectionnez

HWND CreateWindow(
LPCTSTR lpClassName, // Pointeur sur une classe de fenêtre.
LPCTSTR lpWindowName, // Pointeur sur le texte de la fenêtre.
DWORD dwStyle, // Style de la fenêtre.
int x, // Position horizontale de la fenêtre.
int y, // Position verticale de la fenêtre.
int nWidth, // Largeur de la fenêtre.
int nHeight, // Hauteur de la fenêtre.
HWND hWndParent, // Handle de la fenêtre parent.
HMENU hMenu, // Handle de menu ou ID de contrôle.
HANDLE hInstance, // Handle d'instance de l'application.
LPVOID lpParam // Pointeur sur des données passées à WM_CREATE.
);

En voici un exemple d'utilisation :

Sélectionnez

HWND hwnd;
hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
NULL, NULL, hinstance, NULL);

Le premier paramètre qu'elle reçoit est un pointeur sur une chaîne de caractères identifiant la classe de fenêtre. Les classes de fenêtre sont des modéles pour construire les fenêtres (le terme de classe n'a rien à voir avec les classes du C++). Si pour les contrôles standard nous avons des classes de fenêtres prédéfinies et globale, nous devrons en créer une pour la fenêtre principale. Nous devons pour cela remplir une structure de type WNDCLASS (définie dans winuser.h)

Sélectionnez

WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = "MaWinClass";

Les champs qu'il nous importe d'affecter pour l'instant à cette structure sont :

  • Le champ lpszClassName avec un pointeur sur la chaîne de caractères identifiant la classe de fenêtre. Celle que l'on a passé comme paramètre à la fonction CreateWindow.
  • Le champ hInstance sera initialisé avec le handle d'instance hinstance que nous a fourni la fonction WinMain.
  • Le champ hIcon avec le handle d'une icône standard de Windows qui nous est retournée par la fonction LoadIcon.
  • Le champ hCursor avec le handle d'un curseur prédéfini de Windows (en l'occurrence la flèche) qui nous est retourné par la fonction LoadCursor.
  • Le champ hbrBackground avec le handle d'un pinceau qui servira à peindre la surface client de la fenêtre. Dans cet exemple nous lui affecterons une couleur prédéfinie du système (1 + (l'identificateur de la couleur)).
  • Le champ lpfnWndProc de type WNDPROC reçoit un pointeur de fonction sur la procédure de fenêtre que nous allons voir plus loin dans ce document.

Nous passons sous silence pour l'instant les autres champs de cette structure.

Remplir cette structure ne suffit pas, il faut aussi l'enregistrer au niveau du sytème (souvenez-vous qu'elles sont identifiées par une chaîne de caractères). Cet enregistrement sera effectué par la fonction RegisterClass qui reçoit comme paramètre l'adresse de la variable wc de type WNDCLASS créée précédemment :

Sélectionnez

RegisterClass(&wc);

Revenons-en maintenant à la fonction CreateWindow.

  • Son deuxième paramètre reçoit un pointeur sur le texte de la fenêtre. Dans le cas de la fenêtre principale, ce sera le texte du titre (si cela avait été un bouton, le texte se serait affiché sur le bouton).
  • Le troisième paramètre est le style de la fenêtre. Ce sont des identificateurs qui sont défini dans le fichier winuser.h. Nous ne les passerons pas en revue car ils sont trop nombreux. Dans l'exemple, nous lui donnerons la valeur WS_OVERLAPPEDWINDOW. Ce paramètre permet d'avoir une fenêtre avec une barre de titre, un menu system, les boutons minimiser et maximiser et que l'on peut redimensionner.
  • Les deux paramètres suivants sont la position à l'écran de la fenêtre par rapport au coin haut gauche du bureau. Dans l'exemple, ils ont la valeur par défaut fournie par Windows : CW_USEDEFAULT.
  • Les deux paramètres suivants sont la taille de la fenêtre : largeur et hauteur. Ils peuvent aussi avoir la valeur par défaut CW_USEDEFAULT.
  • Le paramètre suivant désigne le parent de la fenêtre. Il est utile pour les contrôles enfants. En ce qui concerne la fenêtre principale nous l'initialiserons à NULL.
  • Le paramètre suivant désigne un menu pour la fenêtre, il peut être à NULL s'il n'y a pas de menu ou si le menu a été référencé dans le champ lpszMenuName de la classe de fenêtre.
  • L'avant-dernier paramètre reçoit le handle d'instance de l'application qui crée la fenêtre, celui qui est fourni par WinMain.
  • Le dernier paramètre sert à passer des données à la procédure de fenêtre quand elle reçoit un message WM_CREATE. Dans cet exemple, il sera initialisé à NULL.

Après s'être exécutée, la fonction CreateWindow nous retourne le handle de la fenêtre qu'elle vient de créer. Les handles de fenêtre sont des identificateurs uniques attribués par Windows qui désignent chaque fenêtres. Ils sont de type HWND.

Notre fenêtre étant maintenant créée, il faut la rendre visible par l'appel de la fonction ShowWindow :

Sélectionnez

ShowWindow(hwnd, nCmdShow);

Cette fonction reçoit comme paramètre le handle de la fenêtre et un identificateur qui indique comment on désire l'afficher (normal, caché, minimisé ...). En général, on lui donne celui fourni par WinMain (voir plus haut dans ce document).

Ensuite, on appelle UpdateWindow avec comme paramètre le handle de la fenêtre. Cette fonction sert à rafraîchir l'affichage de la zone client. Dans cet exemple, elle n'est pas vraiment utile puisque la zone client est vide.

Sélectionnez

UpdateWindow(hwnd);

IV. La boucle de messages :▲

Revenons au fonctionnement de Windows. Pour communiquer avec une application ou ses diverses fenêtres, Windows leurs envoie des messages. Par exemple, si vous cliquez sur le bouton fermeture (en haut à droite de la fenêtre), Windows va créer un message approprié qu'il va envoyer dans la file d'attente de l'application. La file d'attente est un tampon où sont stockés les messages en attente de traitement.

C'est à nous de coder l'extraction des messages de la file d'attente :

Sélectionnez

MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

C'est le rôle de la fonction GetMessage, dont le premier paramètre est un pointeur sur une variable de type MSG où elle doit stocker les données du message qu'elle vient de récupérer.

Nous devons donc définir cette variable de type MSG, qui est une structure composé d'un champ hwnd qui contient le handle de la fenêtre à qui il est destiné, un champ message identifiant le message (par exemple WM_CLOSE pour demander à la fenêtre de ce fermer) et de deux champs wParam et lParam contenant des données ayant rapport au message et de significations différentes selon le message.

Revenons à GetMessage,

  • Son deuxième paramètre est le handle de fenêtre dont on veut récupérer les messages. Il sera mis à NULL car on veut récupérer tous les messages de l'application (destinés à toutes ses fenêtres).
  • Les deux derniers paramètres seront mis à zéro, ils servent au filtrage des messages.

GetMessage renvoie toujours la valeur TRUE sauf si elle vient de récupérer le message WM_QUIT, ce qui met fin à la boucle de message donc à l'application.

Ensuite on passe le message à la fonction TranslateMessage qui traduit les messages WM_KEYDOWN (appui des touches clavier) en message WM_CHAR avec le code de caractère correspondant dans le champ wParam du message.

Ensuite, c'est à la fonction DispatchMessage de traiter le message. Son rôle est de l'envoyer à la procédure de fenêtre. Celle à qui il est destiné.

V. La procédure de fenêtre :▲

Voici la procédure de fenêtre de notre fenêtre principale dont nous avions passé un pointeur à la classe de fenêtre au début de ce document (on passe ce pointeur à la classe de fenêtre car c'est nous qui la créons, mais le système d'exploitation qui l'appelle, il doit donc la localiser).

Sélectionnez

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

Les paramètres qu'elle reçoit sont les données du message en cours de traitement. Celui qui a été envoyé par DispatchMessage. C'est à nous, développeurs, d'implémenter des actions en fonction du message que la procédure de fenêtre reçoit. Dans cet exemple, un seul message est traité : WM_DESTROY. Il nous indique que la fenêtre est fermée et a été détruite. Comme il s'agit de la fenêtre principale de l'application, il est d'usage de fermer l'application. Ce que nous ferons en appelant la fonction PostQuitMessage qui a pour fonction de poster un message WM_QUIT dans la file d'attente. Qui comme nous l'avons vu plus haut dans ce document, met fin à la boucle de messages et donc à l'application. Les messages non traités doivent l'être par la fonction DefWindowProc. Fonction qui implémente le comportement par défaut d'une fenêtre.

VI. Code complet :▲

Sélectionnez

#include <windows.h>
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

Ce code a été testé sous VC++, CodeBlocks(Mingw), C++ Builder.

A vos PC.


02 - Contrôles et Messages.htm

I. Introduction▲

Dans ce chapitre, nous allons revenir sur le traitement des messages.
Nous allons aussi créer un contrôle enfant que nous allons mettre sur la fenêtre principale.
Ce contrôle sera un contrôle d'édition multiligne. Il devra se positionner sur toute la surface client de la fenêtre principale (on appelle surface client d'une fenêtre toute sa partie intérieure, c'est-à-dire la fenêtre sans sa bordure et sa barre de titre).

Image non disponible

II. Création d'un contrôle▲

Comme nous l'avons dit au chapitre précédent , un contrôle est une fenêtre. Nous allons donc utiliser comme pour la fenêtre principale la fonction CreateWindow pour le créer.

Sélectionnez

HWND hEdit;
hEdit =CreateWindow("edit", "Texte",
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);

Pour les contrôles, les classes de fenêtres sont globales et définies par Windows. Celle du contrôle d'édition s'appelle : "EDIT". Nous la mettrons donc comme premier paramètre de CreateWindow.
Son deuxième paramètre attend un pointeur sur une chaîne de caractères au cas où vous désiriez qu'il contienne du texte dès sa création.
Le troisième attend les identificateurs de style. Ils sont ici complètement différents de ce qu'on avait mis pour la fenêtre principale :
WS_VISIBLE rend la fenêtre visible dès sa création.
WS_CHILD en fait un contrôle enfant. C'est obligatoire si l'on veut qu'il soit à l'intérieur de notre fenêtre.
ES_MULTILINE multiligne si on veut avoir la possibilité de saisir plusieurs lignes.
ES_WANTRETURN autorise le retour à la ligne automatique.
WS_VSCROLL pour lui mettre un ascenseur vertical.
(Les identificateurs de style étant très nombreux, je vous propose de consulter l'aide Win32 API CreateWindow, ou CreateWindowEx qui permet des styles supplémentaires.)
Les quatre paramètres suivants sont comme pour la fenêtre principale, sa position et ses dimensions. Dans le code complet de l'exemple, elles sont toutes mises à zéro, car le contrôle sera redimensionné automatiquement à chaque changement de dimensions de la fenêtre principale (voir plus loin dans ce document).
Le paramètre suivant est le handle de la fenêtre principale, car elle en est le parent.
Et comme pour la fenêtre principale le handle d'instance de l'application pour l'avant-dernier paramètre.
Dans le code de l'exemple, pour des raisons de simplicité, j'ai créé une variable globale pour le handle d'instance que j'ai initialisé dans la fonction WinMain.

III. Les messages▲

Nous avons dit que notre contrôle d'édition devait se redimensionner pour occuper la surface client de la fenêtre principale. Mais comment savoir que les dimensions de la fenêtre principale ont changé ? Par l'intermédiaire d'un message ! Si un utilisateur a changé les dimensions de la fenêtre, Windows va envoyer un message à cette fenêtre pour l'en informer. Dans ce cas précis, le message est WM_SIZE. C'est à nous de traiter ce message dans la procédure de fenêtre :

Sélectionnez

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hEdit;

switch (uMsg)
{
case WM_CREATE:
hEdit =CreateWindow("edit", "Texte",
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);
return 0;
case WM_SIZE :
MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_DESTROY :
PostQuitMessage(0);
return 0;
default :
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

Comme vous pouvez le voir dans l'exemple nous créons le contrôle d'édition à l'arrivée du message WM_CREATE, message envoyé par Windows quand la création de la fenêtre principale est terminée, mais pas encore visible. À la réception du message WM_SIZE, nous récupérons les dimensions de la surface client de la fenêtre dans le paramètre lParam joint au message. La largeur étant dans son mot de poids faible et la hauteur dans son mot de poids fort. Nous les extrayons à l'aide des macros LOWORD et HIWORD. Et nous affecterons ces dimensions au contrôle d'édition à l'aide de la fonction MoveWindow (son dernier paramètre à TRUE informe Windows qu'il doit redessiner le contrôle après l'appel de la fonction).
Son premier paramètre étant le handle du contrôle d'édition, car c'est lui dont on change les dimensions.
Il est évident que l'on ne va pas passer tous les messages en revue, vous pouvez pour cela consulter l'aide sur L'API Win32. Cet exemple vous donne déjà une idée de la manière dont on intercepte les événements dans Windows. Nous aurons l'occasion de voir et revoir les messages dans les chapitres suivants. Ils sont omniprésents dans Windows. Le moindre événement qui survient et c'est un message qui est envoyé. Il est donc important d'être bien documenté, notamment sur les divers messages existants. Et ils sont nombreux.
Comme dit au chapitre précédent, les messages non traités doivent l'être par la fonction DefWindowProc. C'est une procédure de fenêtre interne qui traite les messages afin de faire le minimum vital pour la fenêtre. Ne serait-ce que simplement l'afficher.

Sélectionnez

Structure MSG :
typedef struct tagMSG {
HWND hwnd; // Handle de la fenêtre de destination.
UINT message; // Le message proprement dit.
WPARAM wParam; // Paramètre dépendant du message.
LPARAM lParam; // Paramètre dépendant du message.
DWORD time; // Heure à laquelle il a été posté.
POINT pt; // Coordonnées du curseur au moment où il a été posté.
} MSG;

IV. Code complet▲

Sélectionnez

#include <windows.h>
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
hinst = hinstance;
wc.style = 0 ;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hEdit;

switch (uMsg)
{
case WM_CREATE:
hEdit =CreateWindow("edit", "Texte",
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);
return 0;
case WM_SIZE :
MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_DESTROY :
PostQuitMessage(0);
return 0;
default :
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

Dans le code de cet exemple j'ai mis le pinceau à NULL(wc.hbrBackground = NULL) dans la classe de fenêtre de la fenêtre principale, sinon un il y a scintillement désagréable sur l'ascenseur du contrôle d'édition lors des redimensionnements.

Ce code a été testé sous VC++, CodeBlocks(Mingw), C++ Builder, DevC++.

À vos PC.


03 - Les commandes et notifications.htm

I. Introduction▲

Dans ce chapitre nous allons aborder un message particulier : WM_COMMAND. Ce message sert à envoyer des commandes. Une commande est envoyée suite à l'action d'un contrôle par l'utilisateur. C'est-à-dire par exemple le clic d'un bouton ou d'une option d'un menu Nous devons donc l'intercepter pour implémenter l'action que nous voulons faire. Dans l'exemple nous créerons un menu qui va nous permettre d'envoyer des commandes utilisateur aux fenêtres de l'application.
Nous verrons aussi qu'un contrôle peut envoyer des informations à l'application avec ce message quand des événements propres à lui-même se produisent. On les appelle des notifications.
Nous aborderons aussi l'envoi de messages par programme.

Image non disponible

II. Création du menu▲

Pour préparer notre exemple, nous allons d'abord créer le menu. Il existe deux solutions pour créer des menus, soit par programme, soit à l'aide de ressources. Les ressources feront l'objet d'un autre chapitre. Nous allons donc utiliser la première solution (à savoir qu'il est plus courant d'utiliser les ressources pour construire un menu).

Sélectionnez

#define IDM_QUIT 1
HMENU hMenu, hSousMenu;
hSousMenu = CreateMenu();
AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");
hMenu = CreateMenu();
AppendMenu(hMenu,MF_POPUP,(UINT)hSousMenu,"Fichier");

Les menus sont créés avec la fonction CreateMenu qui renvoie un handle de menu (HMENU). Dans l'exemple nous créons un menu principal et un sous-menu. Nous créons d'abord le sous-menu, car il devra être ajouté au menu principal. Nous lui ajoutons une option de menu à l'aide de la fonction AppendMenu. Nous pouvons utiliser cette fonction autant de fois que l'on a d'options à rajouter. Elle reçoit comme paramètres :
le handle de menu auquel on veut l'ajouter.
Une constante indiquant son aspect (MF_STRING, car c'est une chaîne de caractères).
L'identificateur de l'option qui la distinguera parmi les autres. C'est une constante de type UINT (unsigned int) que l'on peut nommer pour plus de clarté dans le code (#define IDM_QUIT 1).
Le dernier paramètre est un pointeur sur la chaîne de caractères représentant l'option visuellement dans le menu ("Quitter" dans l'exemple).
Nous créons ensuite le menu principal de la même façon. Nous lui ajoutons le sous-menu créé précédemment toujours avec la fonction AppendMenu en lui passant le handle du sous-menu comme paramètre (troisième paramètre de AppendMenu). On lui donnera un aspect de type popup (deuxième paramètre de AppendMenu à MF_POPUP).
Maintenant, il faut l'ajouter à la fenêtre principale de l'application. Dans l'exemple nous le ferons lors de la création de la fenêtre en passant son handle comme paramètre à la fonction

Sélectionnez

CreateWindow :
hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
NULL, hMenu, hinstance, NULL);

III. Les commandes▲

Comment savoir que l'utilisateur a actionné l'option du menu ?
Et bien en traitant le message WM_COMMAND. Comme nous l'avons dit plus haut, ce message est envoyé par Windows quand une commande utilisateur a été actionnée.
Mais comment savoir que cette commande vient d'une option particulière d'un menu ?
Dans le mot de poids faible du paramètre wParam qui est envoyé avec le message WM_COMMAND : LOWORD(wParam). Il contient l'identificateur que l'on avait attribué à l'option de menu lors de son ajout au menu (IDM_QUIT dans l'exemple) :

Sélectionnez

switch (uMsg)
{
case WM_COMMAND :
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
return 0;

Il suffit alors de faire un test sur ce paramètre si l'on est en train de traiter un message WM_COMMAND et d'agir en conséquence.

IV. Envoyer des messages▲

Dans cet exemple si l'on a affaire à la commande IDM_QUIT nous demanderons à la fenêtre de se fermer en lui envoyant un message WM_CLOSE avec la fonction PostMessage. Eh oui, nous aussi nous pouvons envoyer des messages !
PostMessage permet de déposer des messages dans la file d'attente.
Son premier paramètre est le handle de la fenêtre de destination.
Son second paramètre est l'identificateur du message (WM_CLOSE dans l'exemple).
Les deux paramètres suivants sont les valeurs des champs wParam et lParam du message (wParam et lParam sont inutilisés pour le message WM_CLOSE).
Il existe une autre fonction pour envoyer des messages : SendMessage. Elle reçoit les mêmes paramètres que PostMessage et dans le même ordre. La différence est qu'elle envoie le message directement à la procédure de fenêtre sans passer par la file d'attente. Ensuite, elle attend que le message ait été traité, afin de retourner un résultat. Dans l'exemple nous l'utilisons pour ordonner au contrôle d'édition de changer sa police de caractères :

Sélectionnez

HFONT hFont;
hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);

Nous avons ici récupéré le handle d'une police de caractères prédéfinie de Windows avec la fonction GetStockObject (cette fonction permet de récupérer des objets initialisés au démarrage de Windows, tels des pinceaux, crayons...).
Nous allons lui envoyer un second message pour mettre ses marges intérieures droite et gauche à 5 pixels :

Sélectionnez

SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
MAKELONG(5, 5));

La valeur des marges est envoyée dans un seul paramètre. Il est reconstitué en un DWORD avec la macro MAKELONG. Vous avez dû remarquer que le nom de ce message est préfixé des lettres EM au lieu de WM. C'est tout simplement qu'il s'agit d'un message destiné spécifiquement aux contrôles d'édition. EM comme Edit Message et bien sûr WM comme Window Message qui eux s'adressent à tous les types de fenêtres.

V. Les notifications▲

Pendant le déroulement du programme, les contrôles envoient des messages WM_COMMAND à leur fenêtre parent. Ceci quand ils subissent certains événements. Prenons comme exemple le contrôle d'édition, à chaque fois que vous modifiez son texte, il envoie un message WM_COMMAND avec le paramètre EN_CHANGE, pour nous le notifier. On les appelle les notifications. Elles sont envoyées dans le mot de poids fort du champ wParam : HIWORD(wParam) du message WM_COMMAND. Je vous recommande de consulter l'aide API Win32 pour chaque contrôle, afin vous documenter sur leurs différentes notifications.

Sélectionnez

switch (uMsg)
{
case WM_COMMAND :
if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
return 0;

Dans l'exemple, à la réception de cette notification nous mémoriserons dans une variable ce changement. Variable que nous testerons à la fermeture de la fenêtre principale afin de proposer une boîte de message pour laisser le choix à l'utilisateur de fermer ou non la fenêtre s'il y a eu modification du texte du contrôle d'édition :

Sélectionnez

switch (uMsg)
{
case WM_CLOSE :
if(EditNotChg ||
MessageBox(hwnd,"Le texte a été modifié.\r\nÊtes-vous sûr de \
vouloir fermer l'application ?"
,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
DestroyWindow(hwnd);
return 0;

Nous interceptons pour cela le message WM_CLOSE. Nous testons la variable EditNotChg si elle est à TRUE, DestroyWindow est appelé, ce qui a pour conséquence d'envoyer un message WM_DESTROY et donc de fermer l'application. Si EditNotChg est à FALSE la fonction MessageBox est appelée si elle retourne IDYES, nous appelons de même DestroyWindow, sinon le programme continue.
MessageBox, vous l'avez compris sert à appeler des boîtes de message. Son premier paramètre est le handle de fenêtre de son parent. Le deuxième paramètre est le texte du message ou de la question. Le troisième paramètre est son titre. Le quatrième est une constante indiquant son type. (MB_YESNO | MB_ICONQUESTION) dans l'exemple, car elle possède un bouton "Oui", un bouton "Non" et l'icône de question (voir l'aide API Win32 pour les autres possibilités).

VI. Code complet▲

Sélectionnez

#include <windows.h>
#define IDM_QUIT 1
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
HMENU hMenu, hSousMenu;
hinst = hinstance;
wc.style = 0 ;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hSousMenu = CreateMenu();
AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");
hMenu = CreateMenu();
AppendMenu(hMenu,MF_POPUP,(UINT)hSousMenu,"Fichier");
hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
NULL, hMenu, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hEdit;
static BOOL EditNotChg = TRUE;

switch (uMsg)
{
case WM_CREATE:
{
HFONT hFont;
hEdit = CreateWindow("edit", "Texte",
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);
hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
MAKELONG(5, 5));
return 0;
}
case WM_CLOSE :
if(EditNotChg ||
MessageBox(hwnd,"Le texte a été modifié.\r\nÊtes-vous sûr de \
vouloir fermer l'application ?"
,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
DestroyWindow(hwnd);
return 0;
case WM_COMMAND :
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
return 0;
case WM_SIZE :
MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_DESTROY :
PostQuitMessage(0);
return 0;
default :
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

Comme vous pouvez le constater, les messages sont omniprésents dans Windows.
Ce code a été testé sous VC++, CodeBlocks(Mingw), C++ Builder, DevC++.

À vos PC.


04 - Api Windows Les boîtes de dialogue communes_FICHIER.htm

4 - Les boîtes de dialogue communes.

(Ouverture de fichiers.)

Introduction :

Les boîtes de dialogues communes sont celles qui font partie de Windows, comme les boîtes de dialogue d'ouverture ou de sauvegarde de fichier, de choix de polices de caractères, de choix de couleurs... Dans ce premier exemple nous metterons en oeuvre la boîte de dialogue d'ouverture de fichiers. Le principe est semblable pour les autres boîtes de dialogue.

Préparation de l'exemple :

Nous continuons sur la base de l'exemple du chapitre précédent . Nous ajoutons une option avec le texte "Ouvrir..." plus un séparateur au sous-menu. L'option "Ouvrir..." sera identifiée par la constante IDM_OPEN :

#define IDM_QUIT 1
#define IDM_OPEN 2
hSousMenu = CreateMenu();
AppendMenu(hSousMenu, MF_STRING, IDM_OPEN, "Ouvrir...");
AppendMenu(hSousMenu, MF_SEPARATOR, 0, NULL);
AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");

Comme vu au chapitre précédent, son action sera interceptée dans la procédure de fenêtre avec un message WM_COMMAND :

switch (uMsg)
{
case WM_COMMAND:
if(LOWORD(wParam) == IDM_OPEN)
{
/* Appel de la boîte de dialogue ici. */

La boîte de dialogue commune :

Avant d'appeler une boîte de dialogue commune, nous devont remplir une structure de données la concernant. Elle sera passée comme paramètre à la fonction d'appel. Dans le cas de la boîte de dialogue destinée à l'ouverture de fichiers la structure se nomme OPENFILENAME :

OPENFILENAME ofn;
CHAR szFile[MAX_PATH]={0};
ZeroMemory(&ofn, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.lpstrFile = szFile;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFilter =
"Fichier source C\0*.c\0Fichier source CPP\0*.cpp\0";
ofn.nFilterIndex = 1;
ofn.Flags =
OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

Nous déclarons donc en premier lieu une variable du type de cette structure : ofn. Comme nous allons remplir seulement les champs qui nous intéressent, nous metterons la totalité de la structure à zéro. Ceci étant effectué en une seule opération par l'appel de la fonction ZeroMemory.
Ensuite, nous affectons son champ lpstrFile avec l'adresse d'un tableau de caractères que nous avons préalablement défini, pour y récupérer le nom et le chemin du fichier. Attention, il doit contenir une chaine valide avant l'appel de la fonction, car elle l'utilise aussi pour initialiser le champ d'édition : "Nom du fichier" de la boîte de dialogue.
Le champ lStructSize doit recevoir la taille de la structure.
Le champ hwndOwner le handle de la fenêtre parent.
Le champ nMaxFile la taille du tableau de caractère recevant le nom du fichier.
Le champ lpstrFilter avec l'adresse d'un tableau de caractère contenant des filtres de fichiers par leur extention. Les textes des filtres et les filtres étant séparés par des caractères null et le tableau ce terminant par deux caractères null (ce type de tableau de caractères est assez fréquent dans Windows).
Le champ nFilterIndex avec l'index du filtre que l'on désire afficher dans le champ d'édition "Fichier de type" à l'ouverture de la boîte de dialogue.
Le champ Flags avec différentes constantes qui influence sur son aspect ou ses fonctionnalités (voir l'aide API Win32).
Une fois les champs de la structure correctement rempli, nous appelons la fonction GetOpenFileName avec l'adresse de la structure précédement rempli comme paramètre. Si elle nous retourne la valeur TRUE, ce qui indique que l'utilisateur à fermé la boîte de dialogue avec le bouton "Ouvrir", nous pourons récupérer le nom et chemin du fichier sélectionné dans le buffer szFile afin de charger le fichier.

if (GetOpenFileName(&ofn)==TRUE)
{
HANDLE hf;
DWORD FileSize,nbcharRead ;
CHAR *buffer;
hf = CreateFile(szFile, GENERIC_READ, 0,NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
FileSize = GetFileSize(hf, NULL);
buffer = (PCHAR)LocalAlloc(LMEM_FIXED, FileSize+1);
ReadFile(hf, buffer, FileSize, &nbcharRead, NULL) ;
buffer[FileSize] = 0;
SendMessage(hEdit, WM_SETTEXT, 0, (LPARAM)buffer);
LocalFree(buffer);
CloseHandle(hf);
}

Connaissant maintenant le nom et le chemin du fichier, nous allons l'ouvrir avec la fonction de Windows CreateFilequi retourne un handle de fichier. Je vous invite à consuler l'aide API Win32 pour plus de détails sur cette fonction qui offre de nombreuses possibilités. Nous récupérons la taille du fichier avec la fonction GetFileSize. Nous créons un buffer de la taille du fichier + 1 octet avec la fonction de Windows LocalAlloc. Nous remplissons ce buffer avec le contenu du fichier à l'aide de la fonction ReadFile. Comme c'est un fichier texte nous ajoutons le zéro terminal au dernier élément du buffer pour en faire une chaîne de caractères.
Nous la copions dans le champs d'édition multi lignes, en lui envoyant un message WM_SETTEXT avec l'adresse du buffer comme dernier paramètre de SendMessage. Pour terminer, nous libérons la mémoire avec la fonction LocalFree et fermons le fichier avec la fonction CloseHandle.

Code complet :

#include <windows.h>
#define IDM_QUIT 1
#define IDM_OPEN 2
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
HMENU hMenu, hSousMenu;
hinst = hinstance;
wc.style = 0 ;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hSousMenu = CreateMenu();
AppendMenu(hSousMenu, MF_STRING, IDM_OPEN, "Ouvrir...");
AppendMenu(hSousMenu, MF_SEPARATOR, 0, NULL);
AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");
hMenu = CreateMenu();
AppendMenu(hMenu,MF_POPUP,(UINT)hSousMenu,"Fichier");
hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 640, 300,
NULL, hMenu, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hEdit;
static BOOL EditNotChg = TRUE;
switch (uMsg)
{
case WM_CREATE:
{
HFONT hFont;
hEdit = CreateWindow("edit", "Texte", WS_CHILD | WS_VISIBLE |
ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);
hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
MAKELONG(5, 5));
return 0;
}
case WM_CLOSE:
if(EditNotChg ||
MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
DestroyWindow(hwnd);
return 0;
case WM_COMMAND:
if(LOWORD(wParam) == IDM_OPEN)
{
OPENFILENAME ofn;
CHAR szFile[MAX_PATH]={0};
ZeroMemory(&ofn, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.lpstrFile = szFile;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFilter =
"Fichier source C\0*.c\0Fichier source CPP\0*.cpp\0";
ofn.nFilterIndex = 1;
ofn.Flags =
OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
if (GetOpenFileName(&ofn)==TRUE)
{
HANDLE hf;
DWORD FileSize,nbcharRead ;
CHAR *buffer;
hf = CreateFile(szFile, GENERIC_READ, 0,NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
FileSize = GetFileSize(hf, NULL);
buffer = (PCHAR)LocalAlloc(LMEM_FIXED, FileSize+1);
ReadFile(hf, buffer, FileSize, &nbcharRead, NULL) ;
buffer[FileSize] = 0;
SendMessage(hEdit, WM_SETTEXT, 0, (LPARAM)buffer);
LocalFree(buffer);
CloseHandle(hf);
}
}
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
return 0;
case WM_SIZE:
MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

En annexe voici 3 autres boîtes de dialogue commune :
- La boîte de dialogue de sauvegarde.
- La boîte de dialogue de choix de police de caractères.
- La boîte de dialogue de choix de couleur.

J'ai testé les compilations avec C++ Builder 4 (Win 95), C++ Builder 6 (Win XP), et DevC++ (Win XP).

Remarques :

Compilé avec BCB6, la boîte de dialogue ne s'ouvrait pas si le programme était exécuté sous une plateforme Win95. Une erreur CDERR_STRUCTSIZE était retourné. Aprés une recherche sur le Web, j'ai trouvé que les compilations faite pour Win 2000 pouvait causer ce problème si les fichier header de Windows était recent. Je n'ai pas eu ce problème avec DevC++ avec une compilation sous Win XP.
La solution pour parrer à ce problème est d'intitaliser le champs lStructSize de la structure OPENFILENAME avec la constante : OPENFILENAME_SIZE_VERSION_400.

ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;

Voir une compilation conditionnelle :

#ifdef OPENFILENAME_SIZE_VERSION_400
ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
#else
ofn.lStructSize = sizeof(OPENFILENAME);
#endif

A tester selon vos configuration.

A vos PC.

CGi

Avec la contribution de nico-pyright(c) pour la relecture.


Api Windows : Boîte de dialogue de choix de couleurs.

API Windows en C

4 - Les boîtes de dialogue communes.

(Choix de couleurs.)

Par CGi

Le 23 juin 2005

Introduction :

En annexe au chapitre : "Les boîtes de dialogue communes", voici une mise en application de la boîte de dialogue de choix de couleur.

Préparation de l'exemple :

Sur la base du même exemple. Nous ajoutons une option avec le texte "Couleur du texte..." au sous-menu. L'option "Couleur du texte..." sera identifiée par la constante IDM_TEXTCOLOR :

#define IDM_QUIT 1
#define IDM_OPEN 2
#define IDM_TEXTCOLOR 3

    /*...       */

    hSousMenu = CreateMenu();
    AppendMenu(hSousMenu, MF_STRING, IDM_OPEN, "Ouvrir...");
    AppendMenu(hSousMenu, MF_STRING, IDM_TEXTCOLOR, "Couleur du texte...");
    AppendMenu(hSousMenu, MF_SEPARATOR, 0, NULL);
    AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");

Nous nous en servirons pour changer la couleur du texte du contrôle d'édition multi lignes.
Pour changer cette couleur nous emploierons la fonction SetTextColor. Elle reçoit comme paramètre un handle de contexte de périphérique et une couleur.
Mais, comment obtenir un contexte de périphérique pour le contrôle d'édition ?
Windows avant d'effectuer le dessin des contrôles envoie un message à sa fenêtre parent : WM_CTLCOLOREDIT pour le contrôle d'édition et y joint son handle de contexte de périphérique (HDC) dans le paramètre wParam et son handle de fenêtre dans le paramètre lParam.

        case WM_CTLCOLOREDIT:
            if((HWND)lParam == hEdit)
              {
               SetTextColor((HDC)wParam, TextColor);
               SetBkColor((HDC)wParam, BkEditcolor);
               return EditBrush;
              }
            return DefWindowProc(hwnd, uMsg, wParam, lParam);

Quand on traite ce message on doit retourner un pinceau. Dans l'exemple si l'on est en train de traiter ce message pour notre contrôle d'édition, nous changeons la couleur de son texte, de son fond et retournons un pinceau de la même couleur que le fond. Ce pinceau ayant été initialisé à la création de la fenêtre principale :

    static HBRUSH EditBrush;
    static COLORREF BkEditcolor;
    static COLORREF TextColor = 0;

    /*...       */
        case WM_CREATE:
            {
             BkEditcolor = GetSysColor(COLOR_WINDOW);
             EditBrush = CreateSolidBrush(BkEditcolor);

Si nous ne traitons pas le message WM_CTLCOLOREDIT, c'est DefWindowProc qui met les valeurs par défaut pour le contrôle.

La boîte de dialogue de choix couleur :

Comme toutes les boîtes de dialogue commune on utilise une structure (CHOOSECOLOR) pour l'initialiser :

          static COLORREF TextColor = 0;

          /*...       */

                    CHOOSECOLOR cc;
                    static COLORREF acrCustClr[16];

                    ZeroMemory(&cc, sizeof(CHOOSECOLOR));
                    cc.lStructSize = sizeof(CHOOSECOLOR);
                    cc.hwndOwner = hwnd;
                    cc.lpCustColors = NULL;
                    cc.lpCustColors = (LPDWORD) acrCustClr;
                    cc.rgbResult = TextColor;
                    cc.Flags = CC_FULLOPEN | CC_RGBINIT;

Puis on appelle la fonction ChooseColor pour ouvrir la boîte de dialogue :

                if (ChooseColor(&cc)==TRUE)
                        {
                          TextColor = cc.rgbResult;
                          PostMessage(hEdit,WM_SETREDRAW,TRUE,0);
                        }

Elle retourne la couleur choisie dans le champ rgbResult de la variable de type CHOOSECOLOR qu'elle a reçu comme pramètre. Nous nous en servons pour affecter la variable TextColor. La même que l'on avait utilisée pour changer la couleur du controle précédement.

Code complet :

#include <windows.h>

#define IDM_QUIT 1
#define IDM_OPEN 2
#define IDM_TEXTCOLOR 3

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc;
    HMENU hMenu, hSousMenu;

    hinst = hinstance;

    wc.style = 0 ;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

    hSousMenu = CreateMenu();
    AppendMenu(hSousMenu, MF_STRING, IDM_OPEN, "Ouvrir...");
    AppendMenu(hSousMenu, MF_STRING, IDM_TEXTCOLOR, "Couleur du texte...");
    AppendMenu(hSousMenu, MF_SEPARATOR, 0, NULL);
    AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");
    hMenu  = CreateMenu();
    AppendMenu(hMenu,MF_POPUP,(UINT)hSousMenu,"Fichier");

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 640, 300,
                                                   NULL, hMenu, hinstance, NULL);
    if (!hwnd)  return FALSE;

    ShowWindow(hwnd, nCmdShow);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HWND hEdit;
    static HBRUSH EditBrush;
    static COLORREF BkEditcolor;
    static BOOL EditNotChg = TRUE;
    static COLORREF TextColor = 0;

    switch (uMsg)
    {
        case WM_CREATE:
            {
             HFONT hFont;

             hEdit = CreateWindow("edit", "Texte", WS_CHILD | WS_VISIBLE |
                                   ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
                                           0, 0, 0, 0, hwnd, NULL, hinst, NULL);

             BkEditcolor = GetSysColor(COLOR_WINDOW);
             EditBrush = CreateSolidBrush(BkEditcolor);
             hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
             SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
             SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
                                                                MAKELONG(5, 5));
             return 0;
            }

        case WM_CLOSE:
            if(EditNotChg ||
               MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
                            ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                                                            DestroyWindow(hwnd);
            return 0;

        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_OPEN)
              {
                OPENFILENAME ofn;
                CHAR szFile[256]={0};

                ZeroMemory(&ofn, sizeof(OPENFILENAME));
                ofn.lStructSize = sizeof(OPENFILENAME);
                ofn.hwndOwner = hwnd;
                ofn.lpstrFile = szFile;
                ofn.nMaxFile = sizeof(szFile);
                ofn.lpstrFilter =
                           "Fichier source C\0*.c\0Fichier source CPP\0*.cpp\0";
                ofn.nFilterIndex = 1;
                ofn.Flags =
                       OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

                if (GetOpenFileName(&ofn)==TRUE)
                 {
                    HANDLE hf;
                    DWORD FileSize,nbcharRead ;
                    char *buffer;

                    hf = CreateFile(szFile, GENERIC_READ, 0,NULL,
                                    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
                    FileSize = GetFileSize(hf, NULL);
                    buffer = (char*)LocalAlloc(LMEM_FIXED, FileSize+1);
                    ReadFile(hf, buffer, FileSize, &nbcharRead, NULL) ;
                    buffer[FileSize] = 0;
                    SendMessage(hEdit, WM_SETTEXT, 0, (LPARAM)buffer);
                    LocalFree(buffer);
                    CloseHandle(hf);
                 }
              } /*if IDM_OPEN*/

            if(LOWORD(wParam) == IDM_TEXTCOLOR)
              {
                    CHOOSECOLOR cc;
                    static COLORREF acrCustClr[16];

                    ZeroMemory(&cc, sizeof(CHOOSECOLOR));
                    cc.lStructSize = sizeof(CHOOSECOLOR);
                    cc.hwndOwner = hwnd;
                    cc.lpCustColors = NULL;
                    cc.lpCustColors = (LPDWORD) acrCustClr;
                    cc.rgbResult = TextColor;
                    cc.Flags = CC_FULLOPEN | CC_RGBINIT;
                    if (ChooseColor(&cc)==TRUE)
                        {
                          TextColor = cc.rgbResult;
                          PostMessage(hEdit,WM_SETREDRAW,TRUE,0);
                        }
              } /*if IDM_TEXTCOLOR*/

            if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
            if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
            return 0;

        case WM_CTLCOLOREDIT:
            if((HWND)lParam == hEdit)
              {
               SetTextColor((HDC)wParam, TextColor);
               SetBkColor((HDC)wParam, BkEditcolor);
               return EditBrush;
              }
            break;

        case WM_SIZE:
            MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
            return 0;

        case WM_DESTROY:
            DeleteObject(EditBrush);
            PostQuitMessage(0);
            return 0;
    }

   return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

J'ai testé les compilations avec C++ Builder et DevC++.

A vos PC.


Api Windows : contrôles et messages

API Windows en C

4 - Les boîtes de dialogue communes.

(Choix de polices de caractères.)

Par CGi

Le 22 juin 2005

Font dialog :

En annexe au chapitre Boîtes de dialogue communes. Voici le code de l'exemple avec la possibilité de changer la police de caractères du contrôle d'édition. La fonction d'appel de la boîte de dialogue se nomme ChooseFont. Elle utilise une structure CHOOSEFONT pour s'initialiser. Elle a entre autre un champ recevant un pointeur sur une structure LOGFONT. Il permet d'initialiser la boîte de dialogue avec la police en cours et de recevoir celle qui a été choisie dans la boîte de dialogue.

    static HFONT hFont;
    static LOGFONT lf;

    /*...       */

        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_FONT)
              {
                CHOOSEFONT cf;
                ZeroMemory(&cf, sizeof(CHOOSEFONT));
                cf.lStructSize = sizeof (CHOOSEFONT);
                cf.hwndOwner = hwnd;
                cf.lpLogFont = &lf;
                cf.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT;

                if (ChooseFont(&cf))
                 {
                    DeleteObject(hFont);
                    hFont = CreateFontIndirect(&lf);
                    SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
                 }
              }

Pour plus de précisions sur les polices de caractères vous pouvez consulter ce chapitre : Dessiner avec Windows.

Code complet :

#include <windows.h>

#define IDM_QUIT 1
#define IDM_OPEN 2
#define IDM_FONT 3

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc;
    HMENU hMenu, hSousMenu;

    hinst = hinstance;

    wc.style = 0 ;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

    hSousMenu = CreateMenu();
    AppendMenu(hSousMenu, MF_STRING, IDM_OPEN, "Ouvrir...");
    AppendMenu(hSousMenu, MF_STRING, IDM_FONT, "Font...");
    AppendMenu(hSousMenu, MF_SEPARATOR, 0, NULL);
    AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");
    hMenu  = CreateMenu();
    AppendMenu(hMenu,MF_POPUP,(UINT)hSousMenu,"Fichier");

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 640, 300,
                                                   NULL, hMenu, hinstance, NULL);
    if (!hwnd)  return FALSE;

    ShowWindow(hwnd, nCmdShow);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HWND hEdit;
    static HFONT hFont;
    static LOGFONT lf;
    static BOOL EditNotChg = TRUE;

    switch (uMsg)
    {
        case WM_CREATE:
            {
             hEdit = CreateWindow("edit", "Texte", WS_CHILD | WS_VISIBLE |
                                   ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
                                           0, 0, 0, 0, hwnd, NULL, hinst, NULL);

             ZeroMemory(&lf, sizeof(LOGFONT));
             lstrcpy(lf.lfFaceName,"Courier");
             lf.lfHeight = 10;
             hFont = CreateFontIndirect(&lf);

             SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
             SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
                                                                MAKELONG(5, 5));
             return 0;
            }

        case WM_CLOSE:
            if(EditNotChg ||
               MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
                            ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                                                            DestroyWindow(hwnd);
            return 0;

        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_OPEN)
              {
                OPENFILENAME ofn;
                CHAR szFile[MAX_PATH]={0};
                ZeroMemory(&ofn, sizeof(OPENFILENAME));
#ifdef OPENFILENAME_SIZE_VERSION_400
                ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
#else
                ofn.lStructSize = sizeof(OPENFILENAME);
#endif
                ofn.hwndOwner = hwnd;
                ofn.lpstrFile = szFile;
                ofn.nMaxFile = MAX_PATH;
                ofn.lpstrFilter =
                           "Fichier source C\0*.c\0Fichier source CPP\0*.cpp\0";
                ofn.nFilterIndex = 1;
                ofn.Flags =
                       OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
                if (GetOpenFileName(&ofn))
                 {
                    HANDLE hf;
                    DWORD FileSize,nbcharRead ;
                    char *buffer;

                    hf = CreateFile(szFile, GENERIC_READ, 0,NULL,
                                    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
                    FileSize = GetFileSize(hf, NULL);
                    buffer = (char*)LocalAlloc(LMEM_FIXED, FileSize+1);
                    ReadFile(hf, buffer, FileSize, &nbcharRead, NULL) ;
                    buffer[FileSize] = 0;
                    SendMessage(hEdit, WM_SETTEXT, 0, (LPARAM)buffer);
                    LocalFree(buffer);
                    CloseHandle(hf);
                 }
              } /*if IDM_OPEN*/

            if(LOWORD(wParam) == IDM_FONT)
              {
                CHOOSEFONT cf;
                ZeroMemory(&cf, sizeof(CHOOSEFONT));
                cf.lStructSize = sizeof (CHOOSEFONT);
                cf.hwndOwner = hwnd;
                cf.lpLogFont = &lf;
                cf.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT;

                if (ChooseFont(&cf))
                 {
                    DeleteObject(hFont);
                    hFont = CreateFontIndirect(&lf);
                    SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
                 }
              } /*if IDM_FONT*/

            if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
            if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
            return 0;

        case WM_SIZE:
            MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
            return 0;

        case WM_DESTROY:
            DeleteObject(hFont);
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

J'ai testé les compilations avec C++ Builder et DevC++.

A vos PC.


Api Windows : contrôles et messages

API Windows en C

4 - Les boîtes de dialogue communes.

(sauvegarde de fichiers.)

Par CGi

Le 22 juin 2005

Save dialog :

En annexe au chapitre "Boîtes de dialogue communes." Voici le code de l'exemple avec la sauvegarde du contenu du contrôle d'édition en plus. Le principe est identique que l'ouverture de fichier. C'est la même structure qui est utilisé : OPENFILENAME. La fonction d'appel ce nomme GetSaveFileName et s'utilise de la même façon que GetOpenFileName. L'écriture du fichier est réalisée avec la fonction WriteFile.

Code complet :

#include <windows.h>

#define IDM_QUIT 1
#define IDM_OPEN 2
#define IDM_SAVE 3

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc;
    HMENU hMenu, hSousMenu;

    hinst = hinstance;

    wc.style = 0 ;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

    hSousMenu = CreateMenu();
    AppendMenu(hSousMenu, MF_STRING, IDM_OPEN, "Ouvrir...");
    AppendMenu(hSousMenu, MF_STRING, IDM_SAVE, "Enregistrer...");
    AppendMenu(hSousMenu, MF_SEPARATOR, 0, NULL);
    AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");
    hMenu  = CreateMenu();
    AppendMenu(hMenu,MF_POPUP,(UINT)hSousMenu,"Fichier");

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 640, 300,
                                                   NULL, hMenu, hinstance, NULL);
    if (!hwnd)  return FALSE;

    ShowWindow(hwnd, nCmdShow);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HWND hEdit;
    static BOOL EditNotChg = TRUE;

    switch (uMsg)
    {
        case WM_CREATE:
            {
             HFONT hFont;

             hEdit = CreateWindow("edit", "Texte", WS_CHILD | WS_VISIBLE |
                                   ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
                                           0, 0, 0, 0, hwnd, NULL, hinst, NULL);
             hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
             SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
             SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
                                                                MAKELONG(5, 5));
             return 0;
            }

        case WM_CLOSE:
            if(EditNotChg ||
               MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
                            ,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
                                                            DestroyWindow(hwnd);
            return 0;

        case WM_COMMAND:
            if(LOWORD(wParam) == IDM_OPEN)
              {

                OPENFILENAME ofn;
                CHAR szFile[MAX_PATH]={0};
                ZeroMemory(&ofn, sizeof(OPENFILENAME));
#ifdef OPENFILENAME_SIZE_VERSION_400
                ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
#else
                ofn.lStructSize = sizeof(OPENFILENAME);
#endif
                ofn.hwndOwner = hwnd;
                ofn.lpstrFile = szFile;
                ofn.nMaxFile = MAX_PATH;
                ofn.lpstrFilter =
                           "Fichier source C\0*.c\0Fichier source CPP\0*.cpp\0";
                ofn.nFilterIndex = 1;
                ofn.Flags =
                       OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
                if (GetOpenFileName(&ofn)==TRUE)
                 {
                    HANDLE hf;
                    DWORD FileSize,nbcharRead ;
                    char *buffer;

                    hf = CreateFile(szFile, GENERIC_READ, 0,NULL,
                                    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
                    FileSize = GetFileSize(hf, NULL);
                    buffer = (char*)LocalAlloc(LMEM_FIXED, FileSize+1);
                    ReadFile(hf, buffer, FileSize, &nbcharRead, NULL) ;
                    buffer[FileSize] = 0;
                    SendMessage(hEdit, WM_SETTEXT, 0, (LPARAM)buffer);
                    LocalFree(buffer);
                    CloseHandle(hf);
                 }
              } /*if IDM_OPEN*/

            if(LOWORD(wParam) == IDM_SAVE)
              {
                OPENFILENAME ofn;
                CHAR szFile[260]={0};

                ZeroMemory(&ofn, sizeof(OPENFILENAME));
#ifdef OPENFILENAME_SIZE_VERSION_400
                ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
#else
                ofn.lStructSize = sizeof(OPENFILENAME);
#endif
                ofn.hwndOwner = hwnd;
                ofn.lpstrFile = szFile;
                ofn.nMaxFile = sizeof(szFile);
                ofn.lpstrFilter =
                           "Fichier source C\0*.c\0Fichier source CPP\0*.cpp\0";
                ofn.nFilterIndex = 1;
                ofn.Flags =
                       OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
                if (GetSaveFileName(&ofn)==TRUE)
                 {
                    HANDLE hf;
                    DWORD FileSize,nbcharRead ;
                    char *buffer;
                    FileSize = GetWindowTextLength(hEdit);
                    buffer = (char*)LocalAlloc(LMEM_FIXED, FileSize+1);
                    GetWindowText(hEdit, buffer, FileSize+1);
                    hf = CreateFile(szFile, GENERIC_WRITE, 0,NULL,
                                    CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
                    WriteFile(hf, buffer, FileSize, &nbcharRead, NULL) ;
                    CloseHandle(hf);
                    LocalFree(buffer);
                 }
              } /*if IDM_SAVE*/

            if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
            if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
            return 0;

        case WM_SIZE:
             MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
             return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

J'ai testé les compilations avec C++ Builder et DevC++.

A vos PC.


05 - Les contextes de périphériques.htm

I. Introduction▲

Nous allons voir dans ce nouveau chapitre les sorties écrans, ou plutôt l'écriture sur les fenêtres. Sous Windows on parlera plutôt de dessin.
Windows a les facultés de pouvoir dessiner sur n'importe quel périphérique de sortie (écran, imprimante...) sans se préoccuper de comment il fonctionne. Ce sont les drivers qui font le travail.
Pour dessiner sous Windows on doit obtenir du périphérique sur lequel l'on veut dessiner un contexte de périphérique (DC). Pour simplifier, nous allons nous représenter le contexte de périphérique comme une surface de dessin que nous aura fourni la fenêtre sur laquelle l'on veut dessiner.

Image non disponible

II. Dessin▲

Nous allons reprendre l'exemple du chapitre 1. Nous y interceptons le message WM_KEYDOWN qui est envoyé à la procédure de fenêtre quand on appuie sur une touche et qu'elle possède le focus clavier. Pour une fenêtre (ou contrôle) avoir le focus clavier signifie qu'elle est la fenêtre active et que par conséquent, c'est elle qui reçoit les entrées clavier.

Sélectionnez

switch (uMsg)
{
case WM_KEYDOWN :
{
int y;
char st[] = "Bienvenue sur Developpez.com" ;
HDC hdc = GetDC(hwnd);
SetBkMode(hdc, TRANSPARENT);
for(y=10; y <= 200; y += 20)
TextOut(hdc, 10, y, st , lstrlen(st));
ReleaseDC(hwnd, hdc);
return 0;
}

À la réception de ce message, nous écrirons 10 lignes avec la chaîne de caractères : "Bienvenue sur Developpez.com" décalées de 20 pixels verticalement les unes par rapport aux autres. Première opération, obtenir le handle d'un contexte de périphérique (HDC) pour la fenêtre en question à l'aide de la fonction GetDC qui reçoit comme paramètre de handle de la fenêtre sur laquelle l'on veut écrire ou dessiner. Quand on écrit ou dessine sous Windows, le fond est aussi peint par la même occasion. Comme il ne nous importe pas pour l'instant nous le mettrons transparent à l'aide de la fonction SetBkMode qui recevra donc comme paramètres le handle du contexte de périphérique et une constante de mode : TRANSPARENT. Nous pouvons alors écrire notre chaîne de caractères à l'aide de la fonction TextOut qui reçoit le handle du contexte de périphérique comme premier paramètre. Les deux suivants étant les coordonnées d'affichage (x,y). Le suivant étant un pointeur sur la chaîne de caractères à dessiner. Le dernier étant la longueur de la chaîne de caractères retournée par la fonction lstrlen (fonction de l'API Windows).
Nous devons libérer le contexte de périphérique après son utilisation. C'est le rôle de la fonction ReleaseDC.

Mais voilà, un problème se pose, à chaque fois que la fenêtre est cachée par une autre, le texte qui s'y trouve disparaît. Ce qui est fort ennuyeux. Dans le cas présent vous pouvez bien le faire réapparaître en appuyant une autre fois sur une touche, mais ce n'est pas très pratique. Mais alors, comment faire ? Et bien c'est encore un message qui intervient WM_PAINT :

III. Le message WM_PAINT▲

À chaque fois qu'une fenêtre ou une partie de fenêtre a besoin de se redessiner Windows lui envoie un message WM_PAINT. Il nous suffira donc d'intercepter ce message dans sa procédure de fenêtre et d'appeler les fonctions de dessin suite à ce message :

Sélectionnez

case WM_PAINT :
{
int y;
char st[] = "Bienvenue sur Developpez.com" ;
HDC hdc = GetDC(hwnd);
SetBkMode(hdc, TRANSPARENT);
for(y=10; y <= 200; y += 20)
TextOut(hdc, 10, y, st , lstrlen(st));
ReleaseDC(hwnd, hdc);
return 0;
}

Miracle ! Le texte ne disparaît plus.
Il est redessiné à chaque fois que cela est nécessaire.
Il subsiste un scintillement désagréable, surtout lors des redimensionnements. Ceci est dû au fait que la zone client de la fenêtre est entièrement redessinée à chaque message WM_PAINT qu'elle reçoit. Une solution existe pour éliminer ce phénomène. C'est de redessiner seulement la partie de la fenêtre qui en a besoin. On l'appelle cette partie la zone invalide.
Pour cela, il faut obtenir le handle du contexte de périphérique avec la fonction BeginPaint au lieu de GetDC. Cette fonction nécessite un pointeur sur une variable préalablement définie de type (struct PAINTSTRUCT) comme paramètre afin d'y mémoriser des informations.

Sélectionnez

case WM_PAINT :
{
int y;
char st[] = "Bienvenue sur Developpez.com" ;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
SetBkMode(hdc, TRANSPARENT);
for(y=10; y <= 200; y += 20)
TextOut(hdc, 10, y, st , lstrlen(st));
EndPaint(hwnd, &ps);
return 0;
}

Tout handle de contexte de périphérique obtenu avec BeginPaint doit être libéré avec EndPaint.
Remarque : les fonctions BeginPaint et EndPaint ne peuvent être utilisées qu'exclusivement à la réception d'un message WM_PAINT.

IV. Code complet▲

Sélectionnez

#include <windows.h>
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wc.lpszMenuName = NULL;
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
/* mis en commentaire et remplacé par WM_PAINT
case WM_KEYDOWN :
{
int y;
char st[] = "Bienvenue sur Developpez.com" ;
HDC hdc = GetDC(hwnd);
SetBkMode(hdc, TRANSPARENT);
for(y=10; y <= 200; y += 20)
TextOut(hdc, 10, y, st , lstrlen(st));
ReleaseDC(hwnd, hdc);
return 0;
} */
/* mis en commentaire et remplacé par WM_PAINT suivant
case WM_PAINT :
{
int y;
char st[] = "Bienvenue sur Developpez.com" ;
HDC hdc = GetDC(hwnd);
SetBkMode(hdc, TRANSPARENT);
for(y=10; y <= 200; y += 20)
TextOut(hdc, 10, y, st , lstrlen(st));
ReleaseDC(hwnd, hdc);
return 0;
} */
case WM_PAINT :
{
int y;
char st[] = "Bienvenue sur Developpez.com" ;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
SetBkMode(hdc, TRANSPARENT);
for(y=10; y <= 200; y += 20)
TextOut(hdc, 10, y, st , lstrlen(st));
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY :
PostQuitMessage(0);
return 0;
default :
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

Avec la contribution de nico-pyright(c) pour la relecture.

À vos PC.


06 - Dessiner.htm

I. Introduction▲

Après avoir vu les contextes de périphériques, nous allons aborder les principales fonctions de dessin.

Image non disponible

Comme vu au chapitre 5, nous dessinerons suite à la réception du message WM_PAINT dans un contexte de périphérique obtenu avec BeginPaint.

II. Dessin▲

Nous allons continuer sur la base de l'exemple du chapitre précédent. Nous y écrirons tout d'abord la même chaîne de caractères avec TextOut, mais une seule fois. Ensuite, nous tracerons un rectangle au-dessous de la chaîne de caractères :

Sélectionnez

Rectangle(hdc, 20, 60, 120, 100);

Cette fonction reçoit comme paramètres le handle de contexte de périphérique et les coordonnées des points haut gauche et bas droit du rectangle. Windows pour dessiner ce rectangle utilise un crayon et un pinceau. Quand vous obtenez un handle de contexte de périphérique, il possède déjà un crayon et un pinceau. Mais vous pouvez aussi dessiner avec des crayons et des pinceaux que vous avez créés.

Sélectionnez

HBRUSH hbRed, hbrOld;
HPEN hp2px, hpOld;
/*... ...*/
hp2px = CreatePen(PS_SOLID, 2, 0x00FF0000);
hpOld = SelectObject(hdc,hp2px);
hbRed = CreateSolidBrush(0x000000FF);
hbrOld = SelectObject(hdc,hbRed);
Rectangle(hdc, 160, 60, 260, 100);
SelectObject(hdc,hbrOld);
DeleteObject(hbRed);
SelectObject(hdc,hpOld);
DeleteObject(hp2px);

La création d'un crayon se fait avec la fonction CreatePen qui reçoit comme paramètres une constante indiquant le type de trait (continu dans l'exemple), ensuite la largeur du trait (deux pixels) et enfin sa couleur (bleu). Après son appel, elle retourne un handle de crayon.
Le pinceau rouge est créé avec la fonction CreateSolidBrush qui reçoit comme paramètre une couleur. Mais pour pouvoir les utiliser avec un contexte de périphérique, il faut les sélectionner. C'est le rôle de la fonction SelectObject, qui reçoit le handle du contexte de périphérique sur lequel on veut utiliser l'objet (pinceau, crayon...) et le handle de l'objet comme second paramètre. Le second rectangle est donc dessiné avec un contour tracé avec le crayon bleu d'une largeur de deux pixels et son intérieur peint avec le pinceau rouge.
Quand vous en avez terminé avec l'utilisation des objets que vous avez créés, il faut les détruire avec la fonction DeleteObject. De même avant de libérer le contexte de périphérique il faut toujours sélectionner ses objets d'origine avec SelectObject. Le handle de ces objets étant retourné par la fonction SelectObject.

Pour la forme nous dessinerons ensuite un cercle à l'aide le la fonction Ellipse et d'un pinceau à hachures diagonales croisées, puis un trait bleu à l'aide des fonctions MoveToEx qui positionne le point de départ et LineTo qui trace jusqu'au point d'arrivée qu'elle reçoit comme paramètre et qui devient le nouveau point de départ.
Nous n'avons vu que quelques fonctions de dessin, veuillez consulter l'aide API Win32 pour plus de détails.

Sélectionnez

hbGreen = CreateHatchBrush(HS_DIAGCROSS, 0x0000BB00);
SelectObject(hdc,hbGreen);
DeleteObject(hbRed);
Ellipse(hdc, 300, 20, 400, 120);
MoveToEx(hdc, 20, 140, NULL);
LineTo(hdc, 400, 140);

Je vous invite de même à consulter l'aide Win32 API pour voir toutes les possibilités que fournissent les crayons et les pinceaux.

III. Les polices de caractères▲

Nous avons dessiné une chaîne de caractères sur la fenêtre, mais comme vous le savez Windows peut utiliser d'autres polices de caractères installées sur le système telles les polices "True Type".
Pour les utiliser, le principe est semblable aux crayons ou pinceaux. Il faut créer l'objet police avec CreateFont ou CreateFontIndirect qui retournent un handle de police. Dans l'exemple nous utiliserons CreateFontIndirect car elle est plus simple à utiliser. CreateFontIndirect utilise une structure LOGFONT pour initialiser la police. Nous remplirons seulement deux de ses champs dans l'exemple pour qu'il reste simple. Les autres seront initialisés à zéro à l'aide de la fonction ZeroMemory :

Sélectionnez

HFONT NewFont, OldFont;
LOGFONT lf;
/*... ...*/
ZeroMemory(&lf, sizeof(LOGFONT));
lstrcpy(lf.lfFaceName,"Times New Roman");
lf.lfHeight = 36;
NewFont = CreateFontIndirect(&lf);
OldFont = SelectObject(hdc,NewFont);
SetTextColor(hdc, 0x000000FF);
TextOut(hdc, 10, 180, st, lstrlen(st));
SelectObject(hdc,OldFont);
DeleteObject(NewFont);

Première opération, remplir les champs de la variable lf de type LOGFONT. Elle possède un champ lfFaceName : tableau de caractères où nous copions le nom de la police que nous désirons utiliser avec la fonction lstrcpy. Ce tableau a une taille de 32 caractères (y compris le zéro terminal). Nous mettons le champ lfHeight correspondant à la hauteur des caractères à 36. Nous positionnerons le champ lfItalic à true et le champ lfWeightà FW_BOLD pour écrire la chaîne de caractères en gras italique.
Tout comme pour les crayons ou pinceaux, il faut sélectionner les objets Fonts pour les utiliser et les détruire après utilisation, sans oublier de sélectionner la police d'origine avant de libérer le contexte de périphérique.
Pour plus de précisions sur les polices de caractères, veuillez consulter l'aide API Win32.

IV. Code complet▲

Sélectionnez

#include <windows.h>
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wc.lpszMenuName = NULL;
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "Dessiner", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 460, 300,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_PAINT :
{
HBRUSH hbRed, hbGreen, hbrOld;
HPEN hp2px, hpOld;
HFONT NewFont, OldFont;
LOGFONT lf;
char st[] = "Bienvenue sur Developpez.com" ;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
SetBkMode(hdc, TRANSPARENT);
TextOut(hdc, 10, 20, st, lstrlen(st));
Rectangle(hdc, 20, 60, 120, 100);
hp2px = CreatePen(PS_SOLID, 2, 0x00FF0000);
hpOld = SelectObject(hdc,hp2px);
hbRed = CreateSolidBrush(0x000000FF);
hbrOld = SelectObject(hdc,hbRed);
Rectangle(hdc, 160, 60, 260, 100);
hbGreen = CreateHatchBrush(HS_DIAGCROSS, 0x0000BB00);
SelectObject(hdc,hbGreen);
DeleteObject(hbRed);
Ellipse(hdc, 300, 20, 400, 120);
MoveToEx(hdc, 20, 140, NULL);
LineTo(hdc, 400, 140);
SelectObject(hdc,hbrOld);
DeleteObject(hbGreen);
SelectObject(hdc,hpOld);
DeleteObject(hp2px);
ZeroMemory(&lf, sizeof(LOGFONT));
lstrcpy(lf.lfFaceName,"Times New Roman");
lf.lfHeight = 36;
lf.lfItalic = TRUE;
lf.lfWeight = FW_BOLD;
NewFont = CreateFontIndirect(&lf);
OldFont = SelectObject(hdc,NewFont);
SetTextColor(hdc, 0x000000FF);
TextOut(hdc, 10, 180, st, lstrlen(st));
SelectObject(hdc,OldFont);
DeleteObject(NewFont);
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY :
PostQuitMessage(0);
return 0;
default :
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

À vos PC.


07 - Les ressources_.htm

I. Introduction :▲

Dans ce chapitre nous allons aborder les ressources.

Tout d'abord qu'appelle t'on les ressources dans un programme Windows ?

Ce sont des données de toutes natures qui sont incorporées à l'exécutable. Cela peut être des images, des icônes, des chaînes de caractères, des blocs de données de toutes sortes... Mais aussi des boîtes de dialogue et des menus qui seront construits à partir de script.

Image non disponible

II. Incorporation des ressources à l'exécutable :▲

L'incorporation des ressources à l'exécutable est réalisée par le linker. Nous devons lui fournir un fichier contenant les ressources. Ces fichiers ont l'extension ".res". Ils sont compilés par un compilateur de ressources, auquel on fourni un fichier de script de ressources. Les fichiers de script de ressources sont des fichiers texte ayant l'extension ".rc". Il existe aussi des éditeurs de ressources permettant de créer visuellement les fichiers ressources.

Ce tutoriel n'étant pas déstiné à des outils particuliés, nous utiliserons uniquement les scripts de ressources dans les exemples qui vont suivre.

Si vous utilisez un EDI, il suffit d'ajouter les scripts de ressources au projet (pour Dev-C++ il faut les ajouter par le menu "Projects" puis "Projects Options" puis par le bouton "Load Resource", pour C++ Builder il faut tout simplement ajouter les fichiers au projet). La compilation des ressources sera dans ce cas transparente.

Nous allons en voir deux dans ce chapitre : les icônes et les menus.

III. Le script de ressources :▲

Nous allons rependre l'exemple du chapitre 3 - Les commandes et notifications. Nous allons d'abord ajouter des icônes dans les ressources de l'exécutable. Créons tout d'abord un fichier script de ressources nommé "resource.rc" avec le texte suivant :

Sélectionnez

1 ICON icone.ico

2 ICON autre.ico

Chaque ligne est composée de trois éléments, le premier est un identificateur, le second le type de ressource (ICON pour une icône dans l'exemple) le dernier est le contenu, dans le cas de l'exemple ce sera le contenu d'un fichier. Ils doivent bien sur exister. Les ayant mit dans le même dossier que les fichiers sources du programme, je n'ai pas indiqué le chemin pour les localiser. S'ils se trouvent ailleurs il faudra bien sur mettre le chemin (j'ai du tout de même le mettre pour la compilation avec Dev-C++ malgré qu'ils se trouvaient dans le même dossier).

Certain compilateur de resources demande d'inclure les fichiers entêtes de Windows qui contiennent la définition des identificateurs utilisés dans le fichier script de ressources.

Il peux y avoir des différences notables entre les différents compilateurs de ressources. Je vous invite à consulter l'aide de celui que vous utilisez si vous rencontrez des problèmes de compilation.

Je le répète une dernière fois, ce fichier devra être ajouté au projet si vous possédez un EDI ou compilé à l'aide d'un compilateur de ressources et lié à l'exécutable si vous n'avez pas d'EDI.

A ce stade, si vous compilez et que vous visualisez le fichier exécutable dans l'explorateur de fichiers, il devrait avoir la première icône de la liste (ce n'est pas forcement vrai si vous utilisez un EDI, ils peuvent ajouter automatiquement une icône aux ressources de l'exécutable).

Nous allons maintenant mettre la deuxième icône comme icône de la fenêtre (celle qui ce trouve à gauche sur la barre de titre) :

Sélectionnez

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

WNDCLASS wc;

wc.style = 0 ;

wc.lpfnWndProc = MainWndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = NULL;

wc.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(2));

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = NULL;

wc.lpszMenuName = "MyMenu";

wc.lpszClassName = "MaWinClass";

if(!RegisterClass(&wc)) return FALSE;

Pour cela on affecte le champ hIcon de la classe de fenêtre avec une icône que l'on charge avec la fonction LoadIcon. Elle reçoit comme paramètre le handle d'instance de l'application, pour son second paramètre elle attend un pointeur sur une chaîne de caractères identifiant l'icône. Dans le script de ressource nous l'avons identifié avec une valeur numérique. C'est avec la macro MAKEINTRESOURCE que nous faions cette conversion.

Nous aurions pu les identifier avec des valeurs littérales :

Sélectionnez

ICONE1 ICON icone.ico

ICONE2 ICON autre.ico

Dans ce cas LoadIcon aurait reçu un pointeur sur la chaîne de caractère identifiant l'icône dans son second paramètre.

Sélectionnez

wc.hIcon = LoadIcon(hinstance,"ICONE2");

IV. Ressources de type menu :▲

Toujours avec le même exemple, nous allons supprimer le menu que l'on avait créé par programme au chapitre 3 et le créer avec un script de ressources :

Sélectionnez

LEMENU MENU

BEGIN

POPUP "Fichier"

BEGIN

MENUITEM "&Nouveau", IDM_NEW

MENUITEM SEPARATOR

MENUITEM "&Quitter", IDM_QUIT

END

POPUP "Aide"

BEGIN

MENUITEM "A propos...", IDM_ABOUT

END

END

Nous retrouvons là aussi nos trois éléments : l'identificateur de la ressource (littérale dans cet exemple), suivi de son type (MENU : barre de menu) puis de son contenu. Ici le contenu n'est pas un fichier, mais un script de construction de barre de menu, balisé par les mots BEGIN et END. Elle possède deux sous menu (POPUP : menu déroulant) "fichier" et "Aide". Le premier possède trois options (MENUITEM) "&Nouveau", SEPARATOR et "&Quitter". La liste des options d'un sous menu est aussi balisée par les mots BEGIN et END. Chaque option doit être identifiée par une constante numérique. Cette constante sera envoyée à la procédure de fenêtre quand l'option du menu sera actionnée. Il est préférable de les nommer pour plus de clarté dans le code. Comme elles devront être connues du script de ressource et du fichier source du programme, nous les définissons dans un fichier entête séparé, nommé dans cet exemple "resource.h" :

Sélectionnez

#define IDM_QUIT 1

#define IDM_NEW 2

#define IDM_ABOUT 3

Ce fichier devra donc être inclut dans le fichier source et dans le fichier script de ressource (voir code à la fin de ce document). Le menu sera ensuite affecté à la classe de fenêtre. Ceci en affectant au champ lpszMenuName de la classe de fenêtre, un pointeur sur une chaîne de caractères contenant le nom litéral du menu :

Sélectionnez

wc.lpszMenuName = "LEMENU";

C'est tout pour sa construction. La récupération des actions du menu ce fait sur réception des messages WM_COMMAND de la même manière qu'au chapitre 3 :

Sélectionnez

switch (uMsg)

{

case WM_COMMAND:

if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);

/*... */

Vous avez du remarquer que j'ai mis le caractère '&' dans le texte des options de menu "&Nouveau" et "&Quitter". Ce signe sert à souligner le caractère qui le suit et à donner la possibilité d'activer l'option correspondante avec la touche du clavier correspondant à ce caractère. Ceci uniquement quand le popup menu est ouvert. A ne pas confondre avec les accélérateurs clavier que l'on verra lors d'un chapitre ultérieur.

Nous aurons l'occasion de revenir sur les différents types de ressources au cours des chapitres suivants.

V. Code complet :▲

V-A. resource.h :▲

Sélectionnez

#define IDM_QUIT 1

#define IDM_NEW 2

#define IDM_ABOUT 3

V-B. resource.rc :▲

Sélectionnez

#include <windows.h>

#include "resource.h"

1 ICON icone.ico

2 ICON autre.ico

LEMENU MENU

BEGIN

POPUP "Fichier"

BEGIN

MENUITEM "&Nouveau", IDM_NEW

MENUITEM SEPARATOR

MENUITEM "&Quitter", IDM_QUIT

END

POPUP "Aide"

BEGIN

MENUITEM "A propos...", IDM_ABOUT

END

END

V-C. winmain.c :▲

Sélectionnez

#include <windows.h>

#include "resource.h"

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

HWND hwnd;

MSG msg;

WNDCLASS wc;

hinst = hinstance;

wc.style = 0 ;

wc.lpfnWndProc = MainWndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hinstance;

wc.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(2));

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = NULL;

wc.lpszMenuName = "LEMENU";

wc.lpszClassName = "MaWinClass";

if(!RegisterClass(&wc)) return FALSE;

hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,

NULL, NULL, hinstance, NULL);

if (!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow);

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

static HWND hEdit;

static BOOL EditNotChg = TRUE;

switch (uMsg)

{

case WM_CREATE:

{

HFONT hFont;

hEdit = CreateWindow("edit", "Texte",

WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,

0, 0, 0, 0, hwnd, NULL, hinst, NULL);

hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);

SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);

SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,

MAKELONG(5, 5));

return 0;

}

case WM_CLOSE:

if(EditNotChg ||

MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \

vouloir fermer l'application ?"

,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)

DestroyWindow(hwnd);

return 0;

case WM_COMMAND:

if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);

if(LOWORD(wParam) == IDM_NEW)

if(EditNotChg ||

MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \

vouloir fermer votre travail ?"

,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)

{

SendMessage(hEdit,WM_SETTEXT,0,(long)"");

EditNotChg = TRUE;

}

if(LOWORD(wParam) == IDM_ABOUT)

MessageBox(hwnd,"Mon beau programme !","A propos !",MB_OK);

if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;

return 0;

case WM_SIZE:

MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);

return 0;

case WM_DESTROY:

PostQuitMessage(0);

return 0;

default:

return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

}

J'ai testé les compilations avec C++ Builder et DevC++.

A vos PC.


08 - Les boîtes de dialogue personnalisées.htm

I. Introduction▲

Dans ce chapitre nous allons voir comment créer et appeler une boîte de dialogue personnalisée. Nous continuerons avec l'exemple du chapitre précédent. Nous remplacerons la boîte de message appelée par le menu "À propos" par une boîte de dialogue personnalisée contenant un bouton, une icône et un texte.

Image non disponible

II. Le script de ressources▲

Les boîtes de dialogue personnalisées sont créées à partir de ressources :

Sélectionnez

DIALOG1 DIALOG
60, 60, 160, 80
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "A propos"
BEGIN
DEFPUSHBUTTON "Ok", IDOK, 56, 50, 42, 12
ICON 2, -1, 20, 15, 32, 32
LTEXT "Mon beau programme !", -1, 60, 18, 80, 10
END

La ressource est composée de son identificateur littéral, suivi de son type (DIALOG), suivi de ses propriétés (position, dimensions, style et titre), suivi de son contenu balisé par les mots BEGIN et END. Il est en général constitué de contrôles. Chaque contrôle est décrit par son type, suivi de son identification visuelle (texte pour le bouton et le contrôle texte, identificateur de ressource pour l'icône), suivi de sa constante numérique d'identification, puis de sa position dans la boîte de dialogue et enfin de ses dimensions (les largeurs et hauteurs des contrôles ne sont pas en pixels, mais dépendent de la police de caractères utilisée). L'icône et le contrôle texte ont un identificateur à -1 car ils ne sont là que pour la décoration, mais il faut tout de même leur mettre un identificateur. Celui du bouton est à IDOK que nous n'avons pourtant pas défini. IDOK est défini dans Windows, il est envoyé à la procédure de fenêtre de la boîte de dialogue quand on appuie sur la touche "Entrée". Est défini aussi l'identificateur IDCANCEL qui lui est envoyé quand on appuie sur la touche "Echap" ou que l'on tente de fermer la boîte de dialogue.

III. Appel de la boîte de dialogue▲

La boîte de dialogue étant maintenant dans les ressources, nous pouvons l'appeler afin de l'ouvrir. C'est le rôle de la fonction DialogBox. Son premier paramètre est le handle d'instance de l'application, le second, un pointeur sur la chaîne de caractères identifiant la ressource, le troisième est le handle de la fenêtre parent et le dernier un pointeur sur sa procédure de fenêtre.

Sélectionnez

switch (uMsg)
{
case WM_COMMAND :
if(LOWORD(wParam) == IDM_ABOUT)
DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);

IV. Procédure de fenêtre de la boîte de dialogue▲

Comme pour la procédure de fenêtre de la fenêtre principale c'est à vous de la définir. C'est Windows qui l'appellera quand elle aura un message.

Sélectionnez

LRESULT CALLBACK Dialog1Proc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND :
if (LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK)
{
EndDialog(hDlg,0);
return TRUE;
}
default :
return FALSE;
}
}

Comme vous pouvez le constater, elle est fort semblable à celle de la fenêtre principale. Si le message est traité, elle doit renvoyer TRUE, sinon elle doit renvoyer FALSE. WM_INITDIALOG doit être intercepté et renvoyer TRUE afin que les commandes IDOK et IDCANCEL soient envoyées lors des appuis sur les touches correspondantes. Le message WM_INITDIALOG est envoyé par Windows après la création de la boîte de dialogue, mais avant qu'elles soient visibles. Ceci afin de permettre d'initialiser les contrôles qu'elle contient.
Dans l'exemple nous interceptons seulement les commandes IDOK et IDCANCEL afin de la fermer par l'appel de la fonction EndDialog.

V. Code complet▲

resource.h :

Sélectionnez

#define IDM_QUIT 1
#define IDM_NEW 2
#define IDM_ABOUT 3

resource.rc :

Sélectionnez

#include <windows.h>
#include "resource.h"
1 ICON icone.ico
2 ICON autre.ico
LEMENU MENU
BEGIN
POPUP "Fichier"
BEGIN
MENUITEM "Nouveau", IDM_NEW
MENUITEM SEPARATOR
MENUITEM "Quitter", IDM_QUIT
END
POPUP "Aide"
BEGIN
MENUITEM "A propos...", IDM_ABOUT
END
END
DIALOG1 DIALOG
60, 60, 160, 80
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "A propos"
BEGIN
DEFPUSHBUTTON "Ok", IDOK, 56, 50, 42, 12
ICON 2, -1, 20, 15, 32, 32
LTEXT "Mon beau programme !", -1, 60, 18, 80, 10
END

winmain.c :

Sélectionnez

#include <windows.h>
#include "resource.h"
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
hinst = hinstance;
wc.style = 0 ;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(2));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = "LEMENU";
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hEdit;
static BOOL EditNotChg = TRUE;
switch (uMsg)
{
case WM_CREATE:
{
HFONT hFont;
hEdit = CreateWindow("edit", "Texte",
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);
hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
MAKELONG(5, 5));
return 0;
}
case WM_CLOSE :
if(EditNotChg ||
MessageBox(hwnd,"Le texte a été modifié.\r\nÊtes-vous sûr de \
vouloir fermer l'application ?"
,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
DestroyWindow(hwnd);
return 0;
case WM_COMMAND :
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(LOWORD(wParam) == IDM_NEW)
if(EditNotChg ||
MessageBox(hwnd,"Le texte a été modifié.\r\nÊtes-vous sûr de \
vouloir fermer votre travail ?"
,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
{
SendMessage(hEdit,WM_SETTEXT,0,(long)"");
EditNotChg = TRUE;
}
if(LOWORD(wParam) == IDM_ABOUT)
DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);
if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
return 0;
case WM_SIZE :
MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_DESTROY :
PostQuitMessage(0);
return 0;
default :
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
/******************************************************************************/
BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND :
if (LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK)
{
EndDialog(hDlg,0);
return TRUE;
}
default :
return FALSE;
}
}

J'ai testé les compilations avec C++ Builder et DevC++. Le compilateur de ressources de DevC++ a été défaillant pour l'affichage de l'icône dans la boîte de dialogue.

À vos PC.


09 - Api Windows Communiquer avec les boîtes de dialogue_.htm

9 - Communiquer avec les boîtes de dialogue.

Introduction :

Maintenant que nous savons créer une boîte de dialogue, nous allons voir comment initialiser ses contrôles et comment restituer leur contenu à la fenêtre qui a appelé la boîte de dialogue. Nous continuons toujours avec le même exemple où nous ajouterons une option au menu nommé "Paramètres...". Elle appellera une nouvelle boîte de dialogue qui comportera un champ de saisie (EDITTEXT), un bouton "Ok" et un bouton "Annuler". A l'ouverture de la boîte de dialogue, le champ de saisie devra être initialisé avec le titre de la fenêtre principale. A sa fermeture, le titre de la fenêtre principale devra être modifié si la boîte de dialogue est fermée avec le bouton "Ok".

Le script de ressources :

Le script de ressources est fort semblable à celui de la boîte de dialogue du chapitre précédent :

DIALOG2 DIALOG
60, 60, 182, 70
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Paramètres"
BEGIN
DEFPUSHBUTTON "OK", IDOK, 36, 42, 42, 12
PUSHBUTTON "Cancel", IDCANCEL, 96, 42, 42, 12
EDITTEXT IDE_EDIT1, 88, 15, 74, 12
LTEXT "Titre de la fenêtre", -1, 24, 18, 60, 10
END

On peut remarquer deux types de boutons "DEFPUSHBUTTON" et "PUSHBUTTON". Le bouton qui à le type "DEFPUSHBUTTON" est le bouton par défaut. Le contrôle d'édition "EDITTEXT" est identifié par la constante IDE_EDIT1 définie dans le fichier resource.h.

Appel de la boîte de dialogue :

Nous allons appeler la boîte de dialogue avec la fonctionDialogBoxParam. Cette fonction permet de passer un paramètre supplémentaire de notre choix. Il pourra être récupéré dans sa procédure de fenêtre.
Dans l'exemple, nous lui passerons le handle de la fenêtre qui l'appelle.

switch (uMsg)
{
case WM_COMMAND:
if(LOWORD(wParam) == IDM_PARAM)
DialogBoxParam(hinst, "DIALOG2" , hwnd,
(DLGPROC)Dialog2Proc, (LPARAM)hwnd);

Procédure de fenêtre de la boîte de dialogue :

Récupération des données venant de la fenêtre principale :

BOOL APIENTRY Dialog2Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
static HWND hParent;
switch (uMsg)
{
case WM_INITDIALOG:
{
CHAR st[256];
hParent = (HWND)lParam;
GetWindowText(hParent, st, 256);
SetDlgItemText(hDlg, IDE_EDIT1, st);
}
return TRUE;

A la réception du message WM_INITDIALOG nous récupérons le titre de la fenêtre qui l'a appelée avec GetWindowText. Son handle est récupéré dans la variable lParam transmise avec WM_INITDIALOG. C'est le paramètre supplémentaire transmit par la fonction DialogBoxParam. Nous affectons ce titre au champ d'édition avec la fonction SetDlgItemText. Elle reçoit comme paramètres le handle de la boîte de dialogue, puis la constante identifiant le contrôle d'édition, puis le pointeur sur la chaîne de caractères.
Maintenant que notre boîte de dialogue est ouverte, nous pouvons modifier le champ de saisie, puis la fermer.

Si nous la fermons avec le bouton "Ok" :

switch (uMsg)
{
case WM_COMMAND:
if (LOWORD(wParam) == IDOK )
{
CHAR st[256];
GetDlgItemText(hDlg, IDE_EDIT1, st, 256);
SetWindowText(hParent,st);
EndDialog(hDlg,0);
return TRUE;
}

Nous faisons l'opération inverse. Nous récupérons le contenu du champ d'édition avec la fonction GetDlgItemText et nous l'affectons à la fenêtre principale dont on avait mémorisé le handle dans la variable hParent.
Si vous m'avez suivi jusque là, vous vous rendez compte qu'il y a plusieurs possibilités pour transmettre les données entres fenêtres. Celle-ci n'était peut-être pas la meilleure dans cet exemple. On aurait pu utiliser des variables globales ou transmettre un pointeur sur structure dans le paramètre supplémentaire de DialogBoxParam...

Styles étendus des contrôles :

Vous avez remarqué que le contrôle d'édition dans notre boîte de dialogue a un aspect 3D et pas celui qui ce trouve sur la fenêtre principale. Pour donner un aspect 3D à un contrôle qui se trouve sur une fenêtre qui n'est pas une boîte de dialogue, il faut lui donner le style étendu WS_EX_CLIENTEDGE. Pour cela il faut créer le contrôle avec la fonctionCreateWindowEx. Les styles étendus sont donnés dans le premier paramètre de CreateWindowEx. Les autres paramètres de cette fonction sont les mêmes que ceux de CreateWindow.

hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "edit", "Texte",
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);

Code complet :

resource.h :

#define IDM_QUIT 1
#define IDM_NEW 2
#define IDM_ABOUT 3
#define IDM_PARAM 4
#define IDE_EDIT1 101

resource.rc :

#include <windows.h>
#include "resource.h"
1 ICON icone.ico
2 ICON autre.ico
LEMENU MENU
BEGIN
POPUP "Fichier"
BEGIN
MENUITEM "Nouveau", IDM_NEW
MENUITEM "Paramètres...", IDM_PARAM
MENUITEM SEPARATOR
MENUITEM "Quitter", IDM_QUIT
END
POPUP "Aide"
BEGIN
MENUITEM "A propos...", IDM_ABOUT
END
END
DIALOG2 DIALOG
60, 60, 182, 70
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Paramètres"
BEGIN
DEFPUSHBUTTON "OK", IDOK, 36, 42, 42, 12
PUSHBUTTON "Cancel", IDCANCEL, 96, 42, 42, 12
EDITTEXT IDE_EDIT1, 88, 15, 74, 12
LTEXT "Titre de la fenêtre", -1, 24, 18, 60, 10
END
DIALOG1 DIALOG
60, 60, 160, 80
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "A propos"
BEGIN
DEFPUSHBUTTON "Ok", IDOK, 56, 50, 42, 12
ICON 2, -1, 20, 15, 32, 32
LTEXT "Mon beau programme !", -1, 60, 18, 80, 10
END

winmain.c :

#include <windows.h>
#include "resource.h"
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog2Proc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
hinst = hinstance;
wc.style = 0 ;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(2));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = "LEMENU";
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hEdit;
static BOOL EditNotChg = TRUE;
switch (uMsg)
{
case WM_CREATE:
{
HFONT hFont;
hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "edit", "Texte",
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);
hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
MAKELONG(5, 5));
return 0;
}
case WM_CLOSE:
if(EditNotChg ||
MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
DestroyWindow(hwnd);
return 0;
case WM_COMMAND:
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(LOWORD(wParam) == IDM_NEW)
if(EditNotChg ||
MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer votre travail ?"
,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
{
SendMessage(hEdit,WM_SETTEXT,0,(LPARAM)"");
EditNotChg = TRUE;
}
if(LOWORD(wParam) == IDM_ABOUT)
DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);
if(LOWORD(wParam) == IDM_PARAM)
DialogBoxParam(hinst, "DIALOG2" , hwnd,
(DLGPROC)Dialog2Proc, (LPARAM)hwnd);
if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
return 0;
case WM_SIZE:
MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
/******************************************************************************/
BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK)
{
EndDialog(hDlg,0);
return TRUE;
}
default:
return FALSE;
}
}
/******************************************************************************/
BOOL APIENTRY Dialog2Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
static HWND hParent;
switch (uMsg)
{
case WM_INITDIALOG:
{
CHAR st[256];
hParent = (HWND)lParam;
GetWindowText(hParent, st, 256);
SetDlgItemText(hDlg, IDE_EDIT1, st);
}
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK )
{
CHAR st[256];
GetDlgItemText(hDlg, IDE_EDIT1, st, 256);
SetWindowText(hParent,st);
EndDialog(hDlg,0);
return TRUE;
}
if (LOWORD(wParam) == IDCANCEL )
{
EndDialog(hDlg,0);
return TRUE;
}
default:
return FALSE;
}
}

J'ai testé les compilations avec C++ Builder et DevC++.
Voici un exemple semblable avec le contrôle case à cocher : Le contrôle CheckBox ou case à cocher.

A vos PC.

CGi

Avec la contribution de pharaonix pour la relecture.


10 - Les accélérateurs clavier.htm

I. Introduction▲

Les accélérateurs clavier sont des raccourcis clavier qui servent à lancer des actions. Actions qui la plupart du temps peuvent aussi se lancer depuis le menu.
Nous allons continuer sur la base de l'exemple du chapitre précédent. Nous affecterons un raccourci clavier aux commandes des options suivantes du menu : Nouveau, Paramètres, À propos. Quitter a toujours un raccourci clavier qui est "Alt F4" :

Image non disponible

II. Le script de ressources▲

Les accélérateurs clavier sont définis dans les ressources sous forme d'une table :

Sélectionnez

LesAccel ACCELERATORS
BEGIN
"N", IDM_NEW, CONTROL, VIRTKEY
"P", IDM_PARAM, CONTROL, VIRTKEY
VK_F1, IDM_ABOUT, VIRTKEY
END

Le premier élément est le nom littéral de la ressource, le deuxième son type (ACCELERATORS), suivi de son contenu balisé par BEGIN et END. Le contenu est la table des raccourcis clavier proprement dite où chaque raccourci est décrit par la touche concernée décrite sous forme de chaîne de caractères ou de code de touche virtuelle, suivie de la constante d'identification de l'action. Ici les constantes sont les mêmes que les options de menu que l'on veut shunter, suivies de ou des touches étendues : CONTROL, ALT , SHIFT. Et de VIRTKEY ou ASCII.

III. Chargement des tables▲

Pour être fonctionnelles, les tables de raccourcis clavier doivent être chargées par l'application :

Sélectionnez

HACCEL haccel;
haccel = LoadAccelerators(hinstance, "LesAccel");

C'est le rôle de la fonction LoadAccelerators qui reçoit comme paramètres le handle d'instance de l'application suivi du nom de la table. Celui qu'on lui avait donné dans le script de ressources. La fonction nous retourne ensuite un handle de table d'accélérateurs.

Comme le clavier nous n'envoie que des messages d'appui ou de relâchement de touche, nous devons donc traduire ces messages en commande pour les touches faisant partie de la table d'accélérateurs. C'est le rôle de la fonction TranslateAccelerator. Elle reçoit comme paramètres, le handle de la fenêtre, le handle de la table d'accélérateurs et l'adresse du message à traiter. Elle s'utilise dans la boucle de message :

Sélectionnez

while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(hwnd, haccel, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

Quand TranslateAccelerator traite un message WM_KEYDOWN, elle vérifie si le code de la touche se trouve dans la table. Si c'est le cas, elle envoie un message WM_COMMAND à la procédure de fenêtre avec la commande correspondante. Dans ce cas, elle retourne alors la valeur TRUE, que l'on testera afin de ne pas traiter le message WM_KEYDOWN.
Il est d'usage d'indiquer les raccourcis clavier sur le texte des options de menu :

Sélectionnez

LEMENU MENU
BEGIN
POPUP "Fichier"
BEGIN
MENUITEM "Nouveau\tCtrl+N", IDM_NEW
MENUITEM "Paramètres...\tCtrl+P", IDM_PARAM
MENUITEM SEPARATOR
MENUITEM "Quitter\tAlt+F4", IDM_QUIT
END
POPUP "Aide"
BEGIN
MENUITEM "A propos...", IDM_ABOUT
END
END

IV. Code complet▲

resource.h :

Sélectionnez

#define IDM_QUIT 1
#define IDM_NEW 2
#define IDM_ABOUT 3
#define IDM_PARAM 4
#define IDE_EDIT1 101

resource.rc :

Sélectionnez

#include <windows.h>
#include "resource.h"
1 ICON chip.ico
2 ICON factory.ico
LEMENU MENU
BEGIN
POPUP "Fichier"
BEGIN
MENUITEM "Nouveau\tCtrl+N", IDM_NEW
MENUITEM "Paramètres...\tCtrl+P", IDM_PARAM
MENUITEM SEPARATOR
MENUITEM "Quitter\tAlt+F4", IDM_QUIT
END
POPUP "Aide"
BEGIN
MENUITEM "A propos...", IDM_ABOUT
END
END
LesAccel ACCELERATORS
BEGIN
"N", IDM_NEW, CONTROL, VIRTKEY
"P", IDM_PARAM, CONTROL, VIRTKEY
VK_F1, IDM_ABOUT, VIRTKEY
END
DIALOG2 DIALOG
60, 60, 182, 70
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Paramètres"
BEGIN
DEFPUSHBUTTON "OK", IDOK, 36, 42, 42, 12
PUSHBUTTON "Cancel", IDCANCEL, 96, 42, 42, 12
EDITTEXT IDE_EDIT1, 88, 15, 74, 12
LTEXT "Titre de la fenêtre", -1, 24, 18, 60, 10
END
DIALOG1 DIALOG
60, 60, 160, 80
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "A propos"
BEGIN
DEFPUSHBUTTON "Ok", IDOK, 56, 50, 42, 12
ICON 2, -1, 20, 15, 32, 32
LTEXT "Mon beau programme !", -1, 60, 18, 80, 10
END

winmain.c :

Sélectionnez

#include <windows.h>
#include "resource.h"
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog2Proc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
HACCEL haccel;
hinst = hinstance;
wc.style = 0 ;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(2));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = "LEMENU";
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
haccel = LoadAccelerators(hinstance, "LesAccel");
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(hwnd, haccel, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hEdit;
static BOOL EditNotChg = TRUE;
switch (uMsg)
{
case WM_CREATE:
{
HFONT hFont;
hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "edit", "Texte",
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);
hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
MAKELONG(5, 5));
return 0;
}
case WM_CLOSE :
if(EditNotChg ||
MessageBox(hwnd,"Le texte a été modifié.\r\nÊtes-vous sûr de \
vouloir fermer l'application ?"
,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
DestroyWindow(hwnd);
return 0;
case WM_COMMAND :
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(LOWORD(wParam) == IDM_NEW)
if(EditNotChg ||
MessageBox(hwnd,"Le texte a été modifié.\r\nÊtes-vous sûr de \
vouloir fermer votre travail ?"
,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
{
SendMessage(hEdit,WM_SETTEXT,0,(LPARAM)"");
EditNotChg = TRUE;
}
if(LOWORD(wParam) == IDM_ABOUT)
DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);
if(LOWORD(wParam) == IDM_PARAM)
DialogBoxParam(hinst, "DIALOG2" , hwnd,
(DLGPROC)Dialog2Proc, (LPARAM)hwnd);
if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
return 0;
case WM_SIZE :
MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_DESTROY :
PostQuitMessage(0);
return 0;
default :
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
/******************************************************************************/
BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND :
if (LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK)
{
EndDialog(hDlg,0);
return TRUE;
}
default :
return FALSE;
}
}
/******************************************************************************/
BOOL APIENTRY Dialog2Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
static HWND hParent;
switch (uMsg)
{
case WM_INITDIALOG:
{
char st[256];
hParent = (HWND)lParam;
GetWindowText(hParent, st, 255);
SetDlgItemText(hDlg, IDE_EDIT1, st);
}
return TRUE;
case WM_COMMAND :
if (LOWORD(wParam) == IDOK )
{
char st[256];
GetDlgItemText(hDlg, IDE_EDIT1, st, 255);
SetWindowText(hParent,st);
EndDialog(hDlg,0);
return TRUE;
}
if (LOWORD(wParam) == IDCANCEL )
{
EndDialog(hDlg,0);
return TRUE;
}
default :
return FALSE;
}
}

J'ai testé les compilations avec C++ Builder et DevC++.

À vos PC.


11 - Une boîte de dialogue comme fenêtre principale.htm

I. Introduction▲

Un petit chapitre pour montrer qu'on peut utiliser une boîte de dialogue comme fenêtre principale. Cela peut être intéressant pour des applications dont la fenêtre principale comporte beaucoup de contrôles dans sa zone client. Nous allons pour cet exemple créer une boîte de dialogue comportant un contrôle texte, un contrôle d'édition et un bouton. Le contrôle d'édition ne pourra recevoir que des chiffres.

Image non disponible

II. Mise en uvre▲

La boîte de dialogue est construite à partir de ressources comme dans les autres chapitres. Mais dans ce cas nous l'appellerons directement dans la fonction WinMain :

Sélectionnez

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
DialogBox(hInstance,"DIALOG1",NULL,(DLGPROC)DlgProc);
return 0;
}

Le traitement de la boîte de dialogue est identique à une boîte de dialogue ordinaire.

III. Code complet▲

resource.h :

Sélectionnez

#define IDE_EDIT1 101

resource.rc :

Sélectionnez

#include <windows.h>
#include "resource.h"
DIALOG1 DIALOG
60, 60, 160, 80
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Dialogue Principale"
BEGIN
DEFPUSHBUTTON "Ok", IDOK, 56, 55, 42, 12
LTEXT "Saisie d'un nombre :", -1, 45, 15, 80, 10
EDITTEXT IDE_EDIT1, 45, 30, 60, 12, ES_NUMBER | ES_RIGHT
END

winmain.c :

Sélectionnez

#include <windows.h>
#include "resource.h"
BOOL APIENTRY DlgProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
DialogBox(hInstance,"DIALOG1",NULL,(DLGPROC)DlgProc);
return 0;
}
//---------------------------------------------------------------------------
BOOL APIENTRY DlgProc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND :
if (LOWORD(wParam) == IDOK)
{
CHAR st[256];
UINT valeur = GetDlgItemInt(hDlg, IDE_EDIT1, NULL, FALSE);
wsprintf(st, "Vous avez choisi le nombre %d", valeur);
MessageBox(hDlg, st, "Résultat", MB_OK);
}
if (LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg,0);
return TRUE;
}
default :
return FALSE;
}
}

J'ai testé les compilations avec C++ Builder et DevC++.

À vos PC.


12 - Les contrôles supplémentaires de Windows le 27.htm

I. Introduction▲

Windows permet d'utiliser des contrôles supplémentaires gérés par une bibliothèque dynamique appelée : "Common control library" (comctrl32.dll)
Nous allons commencer par le plus simple : la barre de progression. Vous connaissez la barre de progression, celle qui nous fait patienter le temps d'un traitement long.
Nous allons pour cet exemple créer une boîte de dialogue appelée directement depuis la fonction d'entrée WinMain. Elle comportera : une barre de progression et un bouton qui la fera avancer d'une unité à chaque appui.

Image non disponible

II. Mise en uvre▲

Les compilateurs de ressources ne connaissant pas ce contrôle nous utiliserons le type générique CONTROL pour le créer.

Sélectionnez

CONTROL "", IDE_PB1, PROGRESS_CLASS, 0 ,10, 20, 140, 12

Il est suivi du texte, puis de son identificateur, puis du nom de sa classe de fenêtre, puis de son style, puis de sa position et de ses dimensions.
Ces contrôles pour être utilisés doivent s'assurer que comctrl32.dll est chargée. C'est le rôle de la fonction InitCommonControls :

Sélectionnez

InitCommonControls();

Cette fonction doit être appelée une seule fois avant l'utilisation du contrôle.

III. La barre de progression▲

L'utilisation de la barre de progression est très simple. Avant son utilisation, vous lui envoyez un message PBM_SETRANGE pour définir ses bornes :

Sélectionnez

SendDlgItemMessage(hDlg, IDE_PB1, PBM_SETRANGE, 0, MAKELONG(0,10));

Celle-ci dans les mots de poids faible et de poids fort du paramètre lParam, reconstitué avec la macro MAKELONG.
Ensuite, vous initialisez un pas de progression en lui envoyant un message PBM_SETSTEP :

Sélectionnez

SendDlgItemMessage(hDlg, IDE_PB1, PBM_SETSTEP, 1, 0);

Le pas étant passé avec le paramètre wParam du message.
Enfin, vous la faites avancer d'un pas avec le message PBM_STEPIT :

Sélectionnez

SendDlgItemMessage(hDlg, IDE_PB1, PBM_STEPIT, 0, 0);

Ce que nous ferons à chaque action du bouton.

IV. Code complet▲

resource.h :

Sélectionnez

#define IDE_PB1 101

resource.rc :

Sélectionnez

#include <windows.h>
#include "resource.h"
DIALOG1 DIALOG
60, 60, 160, 80
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "ProgressBar"
BEGIN
DEFPUSHBUTTON "Progress", IDOK, 56, 55, 42, 12
CONTROL "", IDE_PB1, PROGRESS_CLASS, 0 ,10, 20, 140, 12
END

winmain.c :

Sélectionnez

#include <windows.h>
#include <commctrl.h>
#include "resource.h"
BOOL APIENTRY DlgProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
InitCommonControls();
DialogBox(hInstance,"DIALOG1",NULL,(DLGPROC)DlgProc);
return 0;
}
/******************************************************************************/
BOOL APIENTRY DlgProc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
SendDlgItemMessage(hDlg, IDE_PB1, PBM_SETRANGE, 0, MAKELONG(0,10));
SendDlgItemMessage(hDlg, IDE_PB1, PBM_SETSTEP, 1, 0);
return TRUE;
case WM_COMMAND :
if (LOWORD(wParam) == IDOK)
{
SendDlgItemMessage(hDlg, IDE_PB1, PBM_STEPIT, 0, 0);
return TRUE;
}
if (LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg,0);
return TRUE;
}
default :
return FALSE;
}
}

Dans le document suivant : "Le contrôle barre d'état" . Un contrôle de la "Common control library" sera créé sur la fenêtre principale.

J'ai testé les compilations avec C++ Builder et Dev-C++.
Le compilateur de ressources de Dev-C++ n'ayant pas accepté la constante PROGRESS_CLASS, j'ai dû la remplacer par son véritable nom de classe de fenêtre.

Sélectionnez

CONTROL "", IDE_PB1, "msctls_progress32", 0 ,10, 20, 140, 12

N'oubliez pas d'ajouter la bibliothèque d'importation "libcomctl32.a" avec Dev-C++ (Load object files dans Project options).

À voir un autre exemple d'utilisation d'un contrôle de comctrl32.dll : La barre d'état .

À vos PC.


13 - Donner le style XP à vos applications_.htm

I. Introduction :▲

Cette article a pour but de vous montrer comment mettre votre propre fichier manifest dans les ressources de vos applications, afin de leur donner le style graphique de Windows XP, sous Windows XP bien sûr. La copie d'écran si dessous est l'exemple de la barre d'état auquel nous avons donnée le style XP.

Image non disponible

II. Mise en application :▲

Pour donner le style des thèmes XP à vos applications, il faut leur associer un fichier xml appelé manifest", dont voici un exemplaire ci-dessous :

manifest.xml :

Sélectionnez

<?xml version="1.0" encoding="UTF-9" standalone="yes"?>

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">

<assemblyIdentity version="1.0.0.0" processorArchitecture="X86"

name="CompanyName.ProductName.YourApplication" type="win32"/>

<description>Description de votre application ici.

<dependency>

<dependentAssembly>

<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls"

version="6.0.0.0" processorArchitecture="X86"

publicKeyToken="6595b64144ccf1df" language="*"/>

</dependentAssembly>

</dependency>

</assembly>

Ce fichier comporte des informations sur votre application, notamment son identité dans les attributs de l'élément assemblyIdentity, sa description dans l'élément description que vous pouvez remplir avec les données concernant votre application. L'élément important pour donner le style XP est l'élément assemblyIdentity de l'élément dependentAssembly de l'élément dependency. Il fournit les informations pour trouver l'assembly fournissant les contrôles au style XP. Il ne doit donc pas être modifié Pour information les assemblies sont des dlls spécifiques à la plateforme DotNet. (C'est un avant goùt la gestion des dlls sur les futurs versions de Windows)

La première solution pour utiliser ce fichier manifest est de le joindre à votre application. Il suffit pour cela de lui donner le même nom que votre exécutable avec l'extension "manifest" et de le déposer dans le même dossier. Exemple : si votre exécutable se nomme "WinMain.exe" le fichier manifest se nommera "WinMain.exe.manifest".

Mais sûrement que vous préfererez qu'il soit intégré l'exécutable. La solution est de le mettre dans ses ressources. Si vous avez fait des recherches sur le Net sur le sujet, vous avez sûrement trouvé à télécharger un fichier de ressource compilé .res à lier à votre exécutable. Nous, nous allons aller un tout petit peu plus loin, nous allons le créer nous-même, ce qui vous permettra d'en personnaliser la partie concernant votre application.

Pour cela nous allons d'abord nommer le fichier manifest en "manifest.xml", puis nous l'ajouterons script de ressources :

Sélectionnez

1 24 manifest.xml

Ici l'identificateur de la ressource est "1" son type est "24" son contenu est celui du fichier manifest.xml (mettre le chemin complet du fichier avec Dev-C++, même s'il ce trouve dans le même dossier).

Pour être un peu plus conventionnel, on aurait pu nommer l'identificateur et le type de la ressource par leurs noms. Normalement ils devraient êtree défini dans le fichier "winuser.h", mais la version de ce fichier jointe avec les compilateurs souvent trop ancienne, fait qu'il ne comporte pas cette définition. Nous pouvons alors par exemple les définir dans un fichier entête nommé "manifest.h".

manifest.h :

Sélectionnez

#ifndef CREATEPROCESS_MANIFEST_RESOURCE_ID

#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1

#endif

#ifndef RT_MANIFEST

#define RT_MANIFEST 24

#endif

Et dans le script de ressources nous l'appelerons alors par :

Sélectionnez

#include "manifest.h"

CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "manifest.xml"

Vous pouvez aussi modifier votre fichier "winuser.h". Voilà nous somme maintenant équipé pour donner du style (XP) à nos nos applications.

A vos PC.


14 - Sous classement de fenêtres_.htm

I. Introduction :▲

Dans une application, vous traitez les messages des fenêtres ordinaires ou des boîtes de dialogue. Les contrôles leur envoient des messages de notification pour signaler un événement propre à eux mêmes. Malheureusement, tous les événements de ces contrôles n'ont pas de messages de notification qui leurs sont associés. Si vous voulez tout de même traiter un de ces événements, une seule solution, accéder à la procédure de fenêtre du contrôle.

Pour cet exemple, nous reprendrons l'exemple du chapitre 2 "Contrôles et Messages" . Nous mettrons le contrôle d'édition en lecture seule, mais pas en lui donnant le style "lecture seule" qui lui donnerait un fond gris. Ce que ne nous ne désirons pas (pour l'exemple).

Image non disponible

II. Préparation de l'exemple:▲

Après avoir créé le contrôle d'édition nous changerons son curseur par celui en forme de flèche. Ceci juste pour montrer visuellement que l'on ne peut pas l'éditer.

Sélectionnez

static HWND hEdit;

switch (uMsg)

{

case WM_CREATE:

hEdit =CreateWindow("edit", "Texte non modifiable.\r\nVous ne pouvez \

que le lire.\r\nMais il garde la couleur du fond d'un contrôle d'édition actif.",

WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,

0, 0, 0, 0, hwnd, NULL, hinst, NULL);

SetClassLong(hEdit, GCL_HCURSOR, (LONG)LoadCursor(NULL, IDC_ARROW));

Cette action étant réalisée en le changeant dans la classe de fenêtre du contrôle. Ceci sera réalisé à l'aide de la fonction SetClassLong. Elle permet de modifier des éléments d'une classe de fenêtre déjà créée.

III. Mise en oeuvre:▲

Accéder à la procédure de fenêtre du contrôle ne sera pas suffisant. On ne pourra en obtenir que seulement un pointeur. Il ne pourra donc servir qu'à l'appeler. Nous devons donc en créer une nouvelle.

Sélectionnez

LRESULT APIENTRY EditProc(HWND, UINT, WPARAM, LPARAM);

Ensuite, on affectera la nouvelle procédure de fenêtre au contrôle d'édition avec la fonction SetWindowLong, qui de plus nous retourne l'adresse de l'ancienne procédure de fenêtre.

Sélectionnez

#ifdef STRICT

WNDPROC wpOrigEditProc ;

#else

FARPROC wpOrigEditProc ;

#endif

/*...

... */

wpOrigEditProc = (PVOID)SetWindowLong(hEdit,

GWL_WNDPROC, (LONG)EditProc);

SetWindowLong permet de modifier les propriétés d'une fenêtre comme son style par exemple.

Le pointeur sur l'ancienne procédure de fenêtre nous est nécessaire pour traiter les messages par défaut du contrôle. Les traitements que l'on veux implémenter se font bien sûr dans la nouvelle procédure de fenêtre du contrôle :

Sélectionnez

LRESULT APIENTRY EditProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

if (uMsg == WM_SETFOCUS || uMsg == WM_CONTEXTMENU || uMsg == WM_CHAR ||

uMsg == WM_KEYDOWN)

return 0;

return CallWindowProc(wpOrigEditProc, hwnd, uMsg, wParam, lParam);

}

Dans cette nouvelle procédure, on doit comme déjà dit, appeler l'ancienne procédure afin d'effectuer les actions par défaut du contrôle, ne serait-ce que de se dessiner par exemple. C'est le rôle de la fonction CallWindowProc qui reçoit bien sûr le pointeur sur l'ancienne procédure de fenêtre et les paramètres du message.

Dans notre exemple on bloquera les messages, WM_SETFOCUS (pour qu'il ne puisse pas prendre le focus), WM_CONTEXTMENU (pour ne pas ouvrir son menu contextuel), WM_KEYDOWN et WM_CHAR (pour qu'il ne puisse pas recevoir des commandes ou des caractères depuis le clavier). Ces messages ne seront donc pas envoyés à la procédure de fenêtre d'origine. Ceci afin d'en faire un contrôle d'édition en lecture seule qui n'est pas gris.

IV. Code complet :▲

Sélectionnez

#include <windows.h>

#ifdef STRICT

WNDPROC wpOrigEditProc ;

#else

FARPROC wpOrigEditProc ;

#endif

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

LRESULT APIENTRY EditProc(HWND, UINT, WPARAM, LPARAM);

HINSTANCE hinst;

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

HWND hwnd;

MSG msg;

WNDCLASS wc;

hinst = hinstance;

wc.style = 0 ;

wc.lpfnWndProc = MainWndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hinstance;

wc.hIcon = NULL;

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = NULL;

wc.lpszMenuName = NULL;

wc.lpszClassName = "MaWinClass";

if(!RegisterClass(&wc)) return FALSE;

hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,

NULL, NULL, hinstance, NULL);

if (!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow);

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

static HWND hEdit;

switch (uMsg)

{

case WM_CREATE:

hEdit =CreateWindow("edit", "Texte non modifiable.\r\nVous ne pouvez \

que le lire.\r\nMais il garde la couleur du fond d'un contrôle d'édition actif.",

WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,

0, 0, 0, 0, hwnd, NULL, hinst, NULL);

SetClassLong(hEdit, GCL_HCURSOR, (LONG)LoadCursor(NULL, IDC_ARROW));

wpOrigEditProc = (PVOID)SetWindowLong(hEdit,

GWL_WNDPROC, (LONG)EditProc);

return 0;

case WM_SIZE:

MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);

return 0;

case WM_DESTROY:

SetWindowLong(hEdit, GWL_WNDPROC, (LONG)wpOrigEditProc);

PostQuitMessage(0);

return 0;

default:

return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

}

/******************************************************************************/

LRESULT APIENTRY EditProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

if (uMsg == WM_SETFOCUS || uMsg == WM_CONTEXTMENU || uMsg == WM_CHAR ||

uMsg == WM_KEYDOWN)

return 0;

return CallWindowProc(wpOrigEditProc, hwnd, uMsg, wParam, lParam);

}

J'ai testé les compilations avec C++ Builder et DevC++.

A vos PC.


15 - Afficher des images.htm

I. Introduction▲

Le traitement d'images dans Windows est un sujet très vaste. Dans ce petit article nous allons voir comment afficher simplement une image dans une fenêtre. Nous avons déjà vu l'affichage d'une image dans une boîte de dialogue à l'aide du contrôle static. Nous allons faire de même, mais dans une fenêtre standard. Nous ne parlerons bien sûr que d'images de type Bitmap (celles dont les fichiers ont l'extension BMP).

Image non disponible

II. Image sur contrôle static▲

Nous le créons comme tout contrôle à l'aide de la fonction CreateWindow avec comme nom de classe de fenêtre "STATIC" suivi du nom de la ressource image (le contrôle static ne peut afficher que des images provenant des ressources du programme). Il devra bien sûr avoir le style SS_BITMAP. Les paramètres de dimensions n'ont aucun effet en ce cas, ils peuvent donc être mis à zéro.

Sélectionnez

case WM_CREATE :
{
HWND hstatic = CreateWindow("STATIC", "MyPicture",
WS_CHILD | WS_VISIBLE| SS_BITMAP,
10, 10, 0, 0, hwnd, NULL, hinst, NULL);
return 0;
}

Le fichier ressource :

Sélectionnez

MyPicture BITMAP "image.bmp"

Cette façon de faire et plus lourde en termes de ressources système, mais a l'avantage que le contrôle peut gérer les messages de notification. Si l'on n'a pas besoin de gérer ces messages, il est préférable de dessiner l'image sur un contexte de périphérique.

III. Dessiner une image avec DrawState▲

Une seconde solution est de dessiner l'image dans la fenêtre en utilisant un contexte de périphérique. Nous chargerons l'image depuis les ressources avec la fonction LoadBitmap et l'afficherons avec la fonction DrawState. Cette solution est moins gourmande en ressources système. Le fichier ressources étant le même que précédemment.

Sélectionnez

case WM_PAINT:
{
HBITMAP hBmp;
HDC hdc;
PAINTSTRUCT ps;
hBmp=LoadBitmap(hinst,"MyPicture");
hdc = BeginPaint(hwnd, &ps);
DrawState(hdc,NULL,NULL,(long)hBmp,NULL,10,10,0,0,DST_BITMAP);
EndPaint(hwnd, &ps);
DeleteObject(hBmp);
return 0;
}

Sous les plateformes Windows 95 et Windows XP, nous pouvons par la même méthode afficher une image provenant d'un fichier (voir note en bas de page pour Windows NT). Pour cela nous chargeons l'image avec la fonction LoadImage.

Sélectionnez

case WM_PAINT :
{
HBITMAP hBmp;
HDC hdc;
PAINTSTRUCT ps;
hBmp=LoadImage(NULL,"image.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
hdc = BeginPaint(hwnd, &ps);
DrawState(hdc,NULL,NULL,(long)hBmp,NULL,10,10,0,0,DST_BITMAP);
EndPaint(hwnd, &ps);
DeleteObject(hBmp);
return 0;
}

IV. Dessiner une image avec BitBlt▲

Une troisième solution est de sélectionner l'image sur un contexte de périphérique en mémoire, puis d'en faire la copie sur le contexte de périphérique d'affichage à l'aide de la fonction BitBlt. Cette solution permet de n'afficher qu'une partie du bitmap d'origine.

Sélectionnez

LONG APIENTRY WndProc(HWND hwnd, UINT uMsg, UINT wParam, LONG lParam)
{
static HBITMAP hBmp;
switch ( uMsg ) {
case WM_CREATE:
{
hBmp=LoadBitmap(hInst,"MyPicture");
return 0;
}
case WM_PAINT:
{
HDC hdc;
PAINTSTRUCT ps;
HBITMAP hbmTmp;
BITMAP bmpi;
HDC hdcMem;
hdcMem = CreateCompatibleDC(NULL);
hbmTmp = SelectObject(hdcMem,hBmp);
GetObject(hBmp,sizeof(bmpi),&bmpi);
hdc = BeginPaint(hwnd, &ps);
BitBlt(hdc,10,10,bmpi.bmWidth,bmpi.bmHeight,hdcMem,0,0,SRCCOPY);
EndPaint(hwnd, &ps);
SelectObject(hdcMem,hbmTmp);
DeleteDC(hdcMem);
return 0;
}
case WM_DESTROY:
DeleteObject(hBmp);
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

Dans cet exemple on a chargé l'image dès la création de l'application. Le contexte de périphérique en mémoire est obtenu avec la fonction CreateCompatibleDC. Son unique paramètre est un contexte de périphérique compatible avec celui avec lequel on travaille. Dans l'exemple il est à NULL, ce qui le rend compatible avec l'écran. On sélectionne le bitmap sur ce contexte de périphérique avec la fonction SelectObject. La fonction BitBlt ayant besoin des dimensions de l'image, nous les obtenons à l'aide de la fonctionGetObject qui les affecte à une structure BITMAP.
La fonction BitBlt reçoit en son premier paramètre le handle du contexte de périphérique de destination. Les deux paramètres suivants sont la position sur la destination. Les deux suivants sont la largeur et la hauteur de la copie. Le 6e étant le handle du contexte de périphérique de la source. Le 7e et le 8e la position de départ (haut, gauche) sur la source. Le dernier étant une constante déterminant le traitement à effectuer. Dans l'exemple c'est une simple copie de la source vers la destination (SRCCOPY). Voir l'aide sur API Win32 pour les différents traitements pouvant être appliqués.

V. Code complet▲

Code complet de la deuxième méthode.
winmain.c :

Sélectionnez

#include <windows.h>
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
hinst = hinstance;
wc.style = 0;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wc.lpszMenuName = NULL;
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "Dessiner", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 280, 240,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_PAINT:
{
HBITMAP hBmp;
HDC hdc;
PAINTSTRUCT ps;
hBmp=LoadBitmap(hinst,"MyPicture");
hdc = BeginPaint(hwnd, &ps);
DrawState(hdc,NULL,NULL,(long)hBmp,NULL,10,10,0,0,DST_BITMAP);
EndPaint(hwnd, &ps);
DeleteObject(hBmp);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

resources.rc :

Sélectionnez

#include
MyPicture BITMAP "image.bmp"

J'ai testé les compilations avec C++ Builder et DevC++.
À vos PC.


16 - Api Windows Les boîtes de dialogue communes.htm

4 - Les boîtes de dialogue communes.

(Ouverture de fichiers.)

Introduction :

Les boîtes de dialogues communes sont celles qui font partie de Windows, comme les boîtes de dialogue d'ouverture ou de sauvegarde de fichier, de choix de polices de caractères, de choix de couleurs... Dans ce premier exemple nous metterons en oeuvre la boîte de dialogue d'ouverture de fichiers. Le principe est semblable pour les autres boîtes de dialogue.

Préparation de l'exemple :

Nous continuons sur la base de l'exemple du chapitre précédent . Nous ajoutons une option avec le texte "Ouvrir..." plus un séparateur au sous-menu. L'option "Ouvrir..." sera identifiée par la constante IDM_OPEN :

#define IDM_QUIT 1
#define IDM_OPEN 2
hSousMenu = CreateMenu();
AppendMenu(hSousMenu, MF_STRING, IDM_OPEN, "Ouvrir...");
AppendMenu(hSousMenu, MF_SEPARATOR, 0, NULL);
AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");

Comme vu au chapitre précédent, son action sera interceptée dans la procédure de fenêtre avec un message WM_COMMAND :

switch (uMsg)
{
case WM_COMMAND:
if(LOWORD(wParam) == IDM_OPEN)
{
/* Appel de la boîte de dialogue ici. */

La boîte de dialogue commune :

Avant d'appeler une boîte de dialogue commune, nous devont remplir une structure de données la concernant. Elle sera passée comme paramètre à la fonction d'appel. Dans le cas de la boîte de dialogue destinée à l'ouverture de fichiers la structure se nomme OPENFILENAME :

OPENFILENAME ofn;
CHAR szFile[MAX_PATH]={0};
ZeroMemory(&ofn, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.lpstrFile = szFile;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFilter =
"Fichier source C\0*.c\0Fichier source CPP\0*.cpp\0";
ofn.nFilterIndex = 1;
ofn.Flags =
OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

Nous déclarons donc en premier lieu une variable du type de cette structure : ofn. Comme nous allons remplir seulement les champs qui nous intéressent, nous metterons la totalité de la structure à zéro. Ceci étant effectué en une seule opération par l'appel de la fonction ZeroMemory.
Ensuite, nous affectons son champ lpstrFile avec l'adresse d'un tableau de caractères que nous avons préalablement défini, pour y récupérer le nom et le chemin du fichier. Attention, il doit contenir une chaine valide avant l'appel de la fonction, car elle l'utilise aussi pour initialiser le champ d'édition : "Nom du fichier" de la boîte de dialogue.
Le champ lStructSize doit recevoir la taille de la structure.
Le champ hwndOwner le handle de la fenêtre parent.
Le champ nMaxFile la taille du tableau de caractère recevant le nom du fichier.
Le champ lpstrFilter avec l'adresse d'un tableau de caractère contenant des filtres de fichiers par leur extention. Les textes des filtres et les filtres étant séparés par des caractères null et le tableau ce terminant par deux caractères null (ce type de tableau de caractères est assez fréquent dans Windows).
Le champ nFilterIndex avec l'index du filtre que l'on désire afficher dans le champ d'édition "Fichier de type" à l'ouverture de la boîte de dialogue.
Le champ Flags avec différentes constantes qui influence sur son aspect ou ses fonctionnalités (voir l'aide API Win32).
Une fois les champs de la structure correctement rempli, nous appelons la fonction GetOpenFileName avec l'adresse de la structure précédement rempli comme paramètre. Si elle nous retourne la valeur TRUE, ce qui indique que l'utilisateur à fermé la boîte de dialogue avec le bouton "Ouvrir", nous pourons récupérer le nom et chemin du fichier sélectionné dans le buffer szFile afin de charger le fichier.

if (GetOpenFileName(&ofn)==TRUE)
{
HANDLE hf;
DWORD FileSize,nbcharRead ;
CHAR *buffer;
hf = CreateFile(szFile, GENERIC_READ, 0,NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
FileSize = GetFileSize(hf, NULL);
buffer = (PCHAR)LocalAlloc(LMEM_FIXED, FileSize+1);
ReadFile(hf, buffer, FileSize, &nbcharRead, NULL) ;
buffer[FileSize] = 0;
SendMessage(hEdit, WM_SETTEXT, 0, (LPARAM)buffer);
LocalFree(buffer);
CloseHandle(hf);
}

Connaissant maintenant le nom et le chemin du fichier, nous allons l'ouvrir avec la fonction de Windows CreateFile qui retourne un handle de fichier. Je vous invite à consuler l'aide API Win32 pour plus de détails sur cette fonction qui offre de nombreuses possibilités. Nous récupérons la taille du fichier avec la fonction GetFileSize. Nous créons un buffer de la taille du fichier + 1 octet avec la fonction de Windows LocalAlloc. Nous remplissons ce buffer avec le contenu du fichier à l'aide de la fonction ReadFile. Comme c'est un fichier texte nous ajoutons le zéro terminal au dernier élément du buffer pour en faire une chaîne de caractères.
Nous la copions dans le champs d'édition multi lignes, en lui envoyant un message WM_SETTEXT avec l'adresse du buffer comme dernier paramètre de SendMessage. Pour terminer, nous libérons la mémoire avec la fonction LocalFree et fermons le fichier avec la fonction CloseHandle.

Code complet :

#include <windows.h>
#define IDM_QUIT 1
#define IDM_OPEN 2
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
HMENU hMenu, hSousMenu;
hinst = hinstance;
wc.style = 0 ;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hSousMenu = CreateMenu();
AppendMenu(hSousMenu, MF_STRING, IDM_OPEN, "Ouvrir...");
AppendMenu(hSousMenu, MF_SEPARATOR, 0, NULL);
AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");
hMenu = CreateMenu();
AppendMenu(hMenu,MF_POPUP,(UINT)hSousMenu,"Fichier");
hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 640, 300,
NULL, hMenu, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hEdit;
static BOOL EditNotChg = TRUE;
switch (uMsg)
{
case WM_CREATE:
{
HFONT hFont;
hEdit = CreateWindow("edit", "Texte", WS_CHILD | WS_VISIBLE |
ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);
hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
MAKELONG(5, 5));
return 0;
}
case WM_CLOSE:
if(EditNotChg ||
MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
DestroyWindow(hwnd);
return 0;
case WM_COMMAND:
if(LOWORD(wParam) == IDM_OPEN)
{
OPENFILENAME ofn;
CHAR szFile[MAX_PATH]={0};
ZeroMemory(&ofn, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.lpstrFile = szFile;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFilter =
"Fichier source C\0*.c\0Fichier source CPP\0*.cpp\0";
ofn.nFilterIndex = 1;
ofn.Flags =
OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
if (GetOpenFileName(&ofn)==TRUE)
{
HANDLE hf;
DWORD FileSize,nbcharRead ;
CHAR *buffer;
hf = CreateFile(szFile, GENERIC_READ, 0,NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
FileSize = GetFileSize(hf, NULL);
buffer = (PCHAR)LocalAlloc(LMEM_FIXED, FileSize+1);
ReadFile(hf, buffer, FileSize, &nbcharRead, NULL) ;
buffer[FileSize] = 0;
SendMessage(hEdit, WM_SETTEXT, 0, (LPARAM)buffer);
LocalFree(buffer);
CloseHandle(hf);
}
}
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
return 0;
case WM_SIZE:
MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

En annexe voici 3 autres boîtes de dialogue commune :
- La boîte de dialogue de sauvegarde.
- La boîte de dialogue de choix de police de caractères.
- La boîte de dialogue de choix de couleur.

J'ai testé les compilations avec C++ Builder 4 (Win 95), C++ Builder 6 (Win XP), et DevC++ (Win XP).

Remarques :

Compilé avec BCB6, la boîte de dialogue ne s'ouvrait pas si le programme était exécuté sous une plateforme Win95. Une erreur CDERR_STRUCTSIZE était retourné. Aprés une recherche sur le Web, j'ai trouvé que les compilations faite pour Win 2000 pouvait causer ce problème si les fichier header de Windows était recent. Je n'ai pas eu ce problème avec DevC++ avec une compilation sous Win XP.
La solution pour parrer à ce problème est d'intitaliser le champs lStructSize de la structure OPENFILENAME avec la constante : OPENFILENAME_SIZE_VERSION_400.

ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;

Voir une compilation conditionnelle :

#ifdef OPENFILENAME_SIZE_VERSION_400
ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
#else
ofn.lStructSize = sizeof(OPENFILENAME);
#endif

A tester selon vos configuration.

A vos PC.

CGi

Avec la contribution de nico-pyright(c) pour la relecture.


17 - Api Windows contrôles et messages.htm

4 - Les boîtes de dialogue communes.

(sauvegarde de fichiers.)

Save dialog :

En annexe au chapitre "Boîtes de dialogue communes." Voici le code de l'exemple avec la sauvegarde du contenu du contrôle d'édition en plus. Le principe est identique que l'ouverture de fichier. C'est la même structure qui est utilisé : OPENFILENAME. La fonction d'appel ce nomme GetSaveFileName et s'utilise de la même façon que GetOpenFileName. L'écriture du fichier est réalisée avec la fonction WriteFile.

Code complet :

#include <windows.h>
#define IDM_QUIT 1
#define IDM_OPEN 2
#define IDM_SAVE 3
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
HMENU hMenu, hSousMenu;
hinst = hinstance;
wc.style = 0 ;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hSousMenu = CreateMenu();
AppendMenu(hSousMenu, MF_STRING, IDM_OPEN, "Ouvrir...");
AppendMenu(hSousMenu, MF_STRING, IDM_SAVE, "Enregistrer...");
AppendMenu(hSousMenu, MF_SEPARATOR, 0, NULL);
AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");
hMenu = CreateMenu();
AppendMenu(hMenu,MF_POPUP,(UINT)hSousMenu,"Fichier");
hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 640, 300,
NULL, hMenu, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hEdit;
static BOOL EditNotChg = TRUE;
switch (uMsg)
{
case WM_CREATE:
{
HFONT hFont;
hEdit = CreateWindow("edit", "Texte", WS_CHILD | WS_VISIBLE |
ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);
hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
MAKELONG(5, 5));
return 0;
}
case WM_CLOSE:
if(EditNotChg ||
MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
DestroyWindow(hwnd);
return 0;
case WM_COMMAND:
if(LOWORD(wParam) == IDM_OPEN)
{
OPENFILENAME ofn;
CHAR szFile[MAX_PATH]={0};
ZeroMemory(&ofn, sizeof(OPENFILENAME));
#ifdef OPENFILENAME_SIZE_VERSION_400
ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
#else
ofn.lStructSize = sizeof(OPENFILENAME);
#endif
ofn.hwndOwner = hwnd;
ofn.lpstrFile = szFile;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFilter =
"Fichier source C\0*.c\0Fichier source CPP\0*.cpp\0";
ofn.nFilterIndex = 1;
ofn.Flags =
OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
if (GetOpenFileName(&ofn)==TRUE)
{
HANDLE hf;
DWORD FileSize,nbcharRead ;
char *buffer;
hf = CreateFile(szFile, GENERIC_READ, 0,NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
FileSize = GetFileSize(hf, NULL);
buffer = (char*)LocalAlloc(LMEM_FIXED, FileSize+1);
ReadFile(hf, buffer, FileSize, &nbcharRead, NULL) ;
buffer[FileSize] = 0;
SendMessage(hEdit, WM_SETTEXT, 0, (LPARAM)buffer);
LocalFree(buffer);
CloseHandle(hf);
}
} /*if IDM_OPEN*/
if(LOWORD(wParam) == IDM_SAVE)
{
OPENFILENAME ofn;
CHAR szFile[260]={0};
ZeroMemory(&ofn, sizeof(OPENFILENAME));
#ifdef OPENFILENAME_SIZE_VERSION_400
ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
#else
ofn.lStructSize = sizeof(OPENFILENAME);
#endif
ofn.hwndOwner = hwnd;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter =
"Fichier source C\0*.c\0Fichier source CPP\0*.cpp\0";
ofn.nFilterIndex = 1;
ofn.Flags =
OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
if (GetSaveFileName(&ofn)==TRUE)
{
HANDLE hf;
DWORD FileSize,nbcharRead ;
char *buffer;
FileSize = GetWindowTextLength(hEdit);
buffer = (char*)LocalAlloc(LMEM_FIXED, FileSize+1);
GetWindowText(hEdit, buffer, FileSize+1);
hf = CreateFile(szFile, GENERIC_WRITE, 0,NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
WriteFile(hf, buffer, FileSize, &nbcharRead, NULL) ;
CloseHandle(hf);
LocalFree(buffer);
}
} /*if IDM_SAVE*/
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
return 0;
case WM_SIZE:
MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

J'ai testé les compilations avec C++ Builder et DevC++.

A vos PC.


Notes sur le langage C

(Choix de polices de caractères.)

Font dialog :

En annexe au chapitre Boîtes de dialogue communes. Voici le code de l'exemple avec la possibilité de changer la police de caractères du contrôle d'édition. La fonction d'appel de la boîte de dialogue se nomme ChooseFont. Elle utilise une structure CHOOSEFONT pour s'initialiser. Elle a entre autre un champ recevant un pointeur sur une structure LOGFONT. Il permet d'initialiser la boîte de dialogue avec la police en cours et de recevoir celle qui a été choisie dans la boîte de dialogue.

static HFONT hFont;
static LOGFONT lf;

/*... */
case WM_COMMAND:
if(LOWORD(wParam) == IDM_FONT)
{
CHOOSEFONT cf;
ZeroMemory(&cf, sizeof(CHOOSEFONT));
cf.lStructSize = sizeof (CHOOSEFONT);
cf.hwndOwner = hwnd;
cf.lpLogFont = &lf;
cf.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT;
if (ChooseFont(&cf))
{
DeleteObject(hFont);
hFont = CreateFontIndirect(&lf);
SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
}
}

Pour plus de précisions sur les polices de caractères vous pouvez consulter ce chapitre : Dessiner avec Windows.

Code complet :

#include <windows.h>
#define IDM_QUIT 1
#define IDM_OPEN 2
#define IDM_FONT 3
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
HMENU hMenu, hSousMenu;
hinst = hinstance;
wc.style = 0 ;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hSousMenu = CreateMenu();
AppendMenu(hSousMenu, MF_STRING, IDM_OPEN, "Ouvrir...");
AppendMenu(hSousMenu, MF_STRING, IDM_FONT, "Font...");
AppendMenu(hSousMenu, MF_SEPARATOR, 0, NULL);
AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");
hMenu = CreateMenu();
AppendMenu(hMenu,MF_POPUP,(UINT)hSousMenu,"Fichier");
hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 640, 300,
NULL, hMenu, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hEdit;
static HFONT hFont;
static LOGFONT lf;
static BOOL EditNotChg = TRUE;
switch (uMsg)
{
case WM_CREATE:
{
hEdit = CreateWindow("edit", "Texte", WS_CHILD | WS_VISIBLE |
ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);
ZeroMemory(&lf, sizeof(LOGFONT));
lstrcpy(lf.lfFaceName,"Courier");
lf.lfHeight = 10;
hFont = CreateFontIndirect(&lf);
SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
MAKELONG(5, 5));
return 0;
}
case WM_CLOSE:
if(EditNotChg ||
MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
DestroyWindow(hwnd);
return 0;
case WM_COMMAND:
if(LOWORD(wParam) == IDM_OPEN)
{
OPENFILENAME ofn;
CHAR szFile[MAX_PATH]={0};
ZeroMemory(&ofn, sizeof(OPENFILENAME));
#ifdef OPENFILENAME_SIZE_VERSION_400
ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
#else
ofn.lStructSize = sizeof(OPENFILENAME);
#endif
ofn.hwndOwner = hwnd;
ofn.lpstrFile = szFile;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFilter =
"Fichier source C\0*.c\0Fichier source CPP\0*.cpp\0";
ofn.nFilterIndex = 1;
ofn.Flags =
OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
if (GetOpenFileName(&ofn))
{
HANDLE hf;
DWORD FileSize,nbcharRead ;
char *buffer;
hf = CreateFile(szFile, GENERIC_READ, 0,NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
FileSize = GetFileSize(hf, NULL);
buffer = (char*)LocalAlloc(LMEM_FIXED, FileSize+1);
ReadFile(hf, buffer, FileSize, &nbcharRead, NULL) ;
buffer[FileSize] = 0;
SendMessage(hEdit, WM_SETTEXT, 0, (LPARAM)buffer);
LocalFree(buffer);
CloseHandle(hf);
}
} /*if IDM_OPEN*/
if(LOWORD(wParam) == IDM_FONT)
{
CHOOSEFONT cf;
ZeroMemory(&cf, sizeof(CHOOSEFONT));
cf.lStructSize = sizeof (CHOOSEFONT);
cf.hwndOwner = hwnd;
cf.lpLogFont = &lf;
cf.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT;
if (ChooseFont(&cf))
{
DeleteObject(hFont);
hFont = CreateFontIndirect(&lf);
SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
}
} /*if IDM_FONT*/
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
return 0;
case WM_SIZE:
MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_DESTROY:
DeleteObject(hFont);
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

J'ai testé les compilations avec C++ Builder et DevC++.

A vos PC.


19 - Api Windows Boîte de dialogue de choix de couleurs_.htm

4 - Les boîtes de dialogue communes.

(Choix de couleurs.)

Introduction :

En annexe au chapitre : "Les boîtes de dialogue communes" , voici une mise en application de la boîte de dialogue de choix de couleur.

Préparation de l'exemple :

Sur la base du même exemple. Nous ajoutons une option avec le texte "Couleur du texte..." au sous-menu. L'option "Couleur du texte..." sera identifiée par la constante IDM_TEXTCOLOR :

#define IDM_QUIT 1
#define IDM_OPEN 2
#define IDM_TEXTCOLOR 3
/*... */
hSousMenu = CreateMenu();
AppendMenu(hSousMenu, MF_STRING, IDM_OPEN, "Ouvrir...");
AppendMenu(hSousMenu, MF_STRING, IDM_TEXTCOLOR, "Couleur du texte...");
AppendMenu(hSousMenu, MF_SEPARATOR, 0, NULL);
AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");

Nous nous en servirons pour changer la couleur du texte du contrôle d'édition multi lignes.
Pour changer cette couleur nous emploierons la fonction SetTextColor. Elle reçoit comme paramètre un handle de contexte de périphérique et une couleur.
Mais, comment obtenir un contexte de périphérique pour le contrôle d'édition ?
Windows avant d'effectuer le dessin des contrôles envoie un message à sa fenêtre parent : WM_CTLCOLOREDIT pour le contrôle d'édition et y joint son handle de contexte de périphérique (HDC) dans le paramètre wParam et son handle de fenêtre dans le paramètre lParam.

case WM_CTLCOLOREDIT:
if((HWND)lParam == hEdit)
{
SetTextColor((HDC)wParam, TextColor);
SetBkColor((HDC)wParam, BkEditcolor);
return EditBrush;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);

Quand on traite ce message on doit retourner un pinceau. Dans l'exemple si l'on est en train de traiter ce message pour notre contrôle d'édition, nous changeons la couleur de son texte, de son fond et retournons un pinceau de la même couleur que le fond. Ce pinceau ayant été initialisé à la création de la fenêtre principale :

static HBRUSH EditBrush;
static COLORREF BkEditcolor;
static COLORREF TextColor = 0;
/*... */
case WM_CREATE:
{
BkEditcolor = GetSysColor(COLOR_WINDOW);
EditBrush = CreateSolidBrush(BkEditcolor);

Si nous ne traitons pas le message WM_CTLCOLOREDIT, c'est DefWindowProc qui met les valeurs par défaut pour le contrôle.

La boîte de dialogue de choix couleur :

Comme toutes les boîtes de dialogue commune on utilise une structure (CHOOSECOLOR) pour l'initialiser :

static COLORREF TextColor = 0;
/*... */
CHOOSECOLOR cc;
static COLORREF acrCustClr[16];
ZeroMemory(&cc, sizeof(CHOOSECOLOR));
cc.lStructSize = sizeof(CHOOSECOLOR);
cc.hwndOwner = hwnd;
cc.lpCustColors = NULL;
cc.lpCustColors = (LPDWORD) acrCustClr;
cc.rgbResult = TextColor;
cc.Flags = CC_FULLOPEN | CC_RGBINIT;

Puis on appelle la fonction ChooseColor pour ouvrir la boîte de dialogue :

if (ChooseColor(&cc)==TRUE)
{
TextColor = cc.rgbResult;
PostMessage(hEdit,WM_SETREDRAW,TRUE,0);
}

Elle retourne la couleur choisie dans le champ rgbResult de la variable de type CHOOSECOLOR qu'elle a reçu comme pramètre. Nous nous en servons pour affecter la variable TextColor. La même que l'on avait utilisée pour changer la couleur du controle précédement.

Code complet :

#include <windows.h>
#define IDM_QUIT 1
#define IDM_OPEN 2
#define IDM_TEXTCOLOR 3
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
HMENU hMenu, hSousMenu;
hinst = hinstance;
wc.style = 0 ;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hSousMenu = CreateMenu();
AppendMenu(hSousMenu, MF_STRING, IDM_OPEN, "Ouvrir...");
AppendMenu(hSousMenu, MF_STRING, IDM_TEXTCOLOR, "Couleur du texte...");
AppendMenu(hSousMenu, MF_SEPARATOR, 0, NULL);
AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");
hMenu = CreateMenu();
AppendMenu(hMenu,MF_POPUP,(UINT)hSousMenu,"Fichier");
hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 640, 300,
NULL, hMenu, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hEdit;
static HBRUSH EditBrush;
static COLORREF BkEditcolor;
static BOOL EditNotChg = TRUE;
static COLORREF TextColor = 0;
switch (uMsg)
{
case WM_CREATE:
{
HFONT hFont;
hEdit = CreateWindow("edit", "Texte", WS_CHILD | WS_VISIBLE |
ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);
BkEditcolor = GetSysColor(COLOR_WINDOW);
EditBrush = CreateSolidBrush(BkEditcolor);
hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
MAKELONG(5, 5));
return 0;
}
case WM_CLOSE:
if(EditNotChg ||
MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
DestroyWindow(hwnd);
return 0;
case WM_COMMAND:
if(LOWORD(wParam) == IDM_OPEN)
{
OPENFILENAME ofn;
CHAR szFile[256]={0};
ZeroMemory(&ofn, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter =
"Fichier source C\0*.c\0Fichier source CPP\0*.cpp\0";
ofn.nFilterIndex = 1;
ofn.Flags =
OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
if (GetOpenFileName(&ofn)==TRUE)
{
HANDLE hf;
DWORD FileSize,nbcharRead ;
char *buffer;
hf = CreateFile(szFile, GENERIC_READ, 0,NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
FileSize = GetFileSize(hf, NULL);
buffer = (char*)LocalAlloc(LMEM_FIXED, FileSize+1);
ReadFile(hf, buffer, FileSize, &nbcharRead, NULL) ;
buffer[FileSize] = 0;
SendMessage(hEdit, WM_SETTEXT, 0, (LPARAM)buffer);
LocalFree(buffer);
CloseHandle(hf);
}
} /*if IDM_OPEN*/
if(LOWORD(wParam) == IDM_TEXTCOLOR)
{
CHOOSECOLOR cc;
static COLORREF acrCustClr[16];
ZeroMemory(&cc, sizeof(CHOOSECOLOR));
cc.lStructSize = sizeof(CHOOSECOLOR);
cc.hwndOwner = hwnd;
cc.lpCustColors = NULL;
cc.lpCustColors = (LPDWORD) acrCustClr;
cc.rgbResult = TextColor;
cc.Flags = CC_FULLOPEN | CC_RGBINIT;
if (ChooseColor(&cc)==TRUE)
{
TextColor = cc.rgbResult;
PostMessage(hEdit,WM_SETREDRAW,TRUE,0);
}
} /*if IDM_TEXTCOLOR*/
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
return 0;
case WM_CTLCOLOREDIT:
if((HWND)lParam == hEdit)
{
SetTextColor((HDC)wParam, TextColor);
SetBkColor((HDC)wParam, BkEditcolor);
return EditBrush;
}
break;
case WM_SIZE:
MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_DESTROY:
DeleteObject(EditBrush);
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

J'ai testé les compilations avec C++ Builder et DevC++.

A vos PC.

CGi


20 - Api Windows Communiquer avec les boîtes de dialogue_ Le controle d'edition.htm

9 - Communiquer avec les boîtes de dialogue.

Introduction :

Maintenant que nous savons créer une boîte de dialogue, nous allons voir comment initialiser ses contrôles et comment restituer leur contenu à la fenêtre qui a appelé la boîte de dialogue. Nous continuons toujours avec le même exemple où nous ajouterons une option au menu nommé "Paramètres...". Elle appellera une nouvelle boîte de dialogue qui comportera un champ de saisie (EDITTEXT), un bouton "Ok" et un bouton "Annuler". A l'ouverture de la boîte de dialogue, le champ de saisie devra être initialisé avec le titre de la fenêtre principale. A sa fermeture, le titre de la fenêtre principale devra être modifié si la boîte de dialogue est fermée avec le bouton "Ok".

Le script de ressources :

Le script de ressources est fort semblable à celui de la boîte de dialogue du chapitre précédent :

DIALOG2 DIALOG
60, 60, 182, 70
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Paramètres"
BEGIN
DEFPUSHBUTTON "OK", IDOK, 36, 42, 42, 12
PUSHBUTTON "Cancel", IDCANCEL, 96, 42, 42, 12
EDITTEXT IDE_EDIT1, 88, 15, 74, 12
LTEXT "Titre de la fenêtre", -1, 24, 18, 60, 10
END

On peut remarquer deux types de boutons "DEFPUSHBUTTON" et "PUSHBUTTON". Le bouton qui à le type "DEFPUSHBUTTON" est le bouton par défaut. Le contrôle d'édition "EDITTEXT" est identifié par la constante IDE_EDIT1 définie dans le fichier resource.h.

Appel de la boîte de dialogue :

Nous allons appeler la boîte de dialogue avec la fonction DialogBoxParam. Cette fonction permet de passer un paramètre supplémentaire de notre choix. Il pourra être récupéré dans sa procédure de fenêtre.
Dans l'exemple, nous lui passerons le handle de la fenêtre qui l'appelle.

switch (uMsg)
{
case WM_COMMAND:
if(LOWORD(wParam) == IDM_PARAM)
DialogBoxParam(hinst, "DIALOG2" , hwnd,
(DLGPROC)Dialog2Proc, (LPARAM)hwnd);

Procédure de fenêtre de la boîte de dialogue :

Récupération des données venant de la fenêtre principale :

BOOL APIENTRY Dialog2Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
static HWND hParent;
switch (uMsg)
{
case WM_INITDIALOG:
{
CHAR st[256];
hParent = (HWND)lParam;
GetWindowText(hParent, st, 256);
SetDlgItemText(hDlg, IDE_EDIT1, st);
}
return TRUE;

A la réception du message WM_INITDIALOG nous récupérons le titre de la fenêtre qui l'a appelée avec GetWindowText. Son handle est récupéré dans la variable lParam transmise avec WM_INITDIALOG. C'est le paramètre supplémentaire transmit par la fonction DialogBoxParam. Nous affectons ce titre au champ d'édition avec la fonction SetDlgItemText. Elle reçoit comme paramètres le handle de la boîte de dialogue, puis la constante identifiant le contrôle d'édition, puis le pointeur sur la chaîne de caractères.
Maintenant que notre boîte de dialogue est ouverte, nous pouvons modifier le champ de saisie, puis la fermer.

Si nous la fermons avec le bouton "Ok" :

switch (uMsg)
{
case WM_COMMAND:
if (LOWORD(wParam) == IDOK )
{
CHAR st[256];
GetDlgItemText(hDlg, IDE_EDIT1, st, 256);
SetWindowText(hParent,st);
EndDialog(hDlg,0);
return TRUE;
}

Nous faisons l'opération inverse. Nous récupérons le contenu du champ d'édition avec la fonction GetDlgItemText et nous l'affectons à la fenêtre principale dont on avait mémorisé le handle dans la variable hParent.
Si vous m'avez suivi jusque là, vous vous rendez compte qu'il y a plusieurs possibilités pour transmettre les données entres fenêtres. Celle-ci n'était peut-être pas la meilleure dans cet exemple. On aurait pu utiliser des variables globales ou transmettre un pointeur sur structure dans le paramètre supplémentaire de DialogBoxParam...

Styles étendus des contrôles :

Vous avez remarqué que le contrôle d'édition dans notre boîte de dialogue a un aspect 3D et pas celui qui ce trouve sur la fenêtre principale. Pour donner un aspect 3D à un contrôle qui se trouve sur une fenêtre qui n'est pas une boîte de dialogue, il faut lui donner le style étendu WS_EX_CLIENTEDGE. Pour cela il faut créer le contrôle avec la fonction CreateWindowEx. Les styles étendus sont donnés dans le premier paramètre de CreateWindowEx. Les autres paramètres de cette fonction sont les mêmes que ceux de CreateWindow.

hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "edit", "Texte",
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);

Code complet :

resource.h :

#define IDM_QUIT 1
#define IDM_NEW 2
#define IDM_ABOUT 3
#define IDM_PARAM 4
#define IDE_EDIT1 101

resource.rc :

#include <windows.h>
#include "resource.h"
1 ICON icone.ico
2 ICON autre.ico
LEMENU MENU
BEGIN
POPUP "Fichier"
BEGIN
MENUITEM "Nouveau", IDM_NEW
MENUITEM "Paramètres...", IDM_PARAM
MENUITEM SEPARATOR
MENUITEM "Quitter", IDM_QUIT
END
POPUP "Aide"
BEGIN
MENUITEM "A propos...", IDM_ABOUT
END
END
DIALOG2 DIALOG
60, 60, 182, 70
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Paramètres"
BEGIN
DEFPUSHBUTTON "OK", IDOK, 36, 42, 42, 12
PUSHBUTTON "Cancel", IDCANCEL, 96, 42, 42, 12
EDITTEXT IDE_EDIT1, 88, 15, 74, 12
LTEXT "Titre de la fenêtre", -1, 24, 18, 60, 10
END
DIALOG1 DIALOG
60, 60, 160, 80
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "A propos"
BEGIN
DEFPUSHBUTTON "Ok", IDOK, 56, 50, 42, 12
ICON 2, -1, 20, 15, 32, 32
LTEXT "Mon beau programme !", -1, 60, 18, 80, 10
END

winmain.c :

#include <windows.h>
#include "resource.h"
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog2Proc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
hinst = hinstance;
wc.style = 0 ;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(2));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = "LEMENU";
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hEdit;
static BOOL EditNotChg = TRUE;
switch (uMsg)
{
case WM_CREATE:
{
HFONT hFont;
hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "edit", "Texte",
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);
hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);
SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
MAKELONG(5, 5));
return 0;
}
case WM_CLOSE:
if(EditNotChg ||
MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer l'application ?"
,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
DestroyWindow(hwnd);
return 0;
case WM_COMMAND:
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(LOWORD(wParam) == IDM_NEW)
if(EditNotChg ||
MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \
vouloir fermer votre travail ?"
,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)
{
SendMessage(hEdit,WM_SETTEXT,0,(LPARAM)"");
EditNotChg = TRUE;
}
if(LOWORD(wParam) == IDM_ABOUT)
DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);
if(LOWORD(wParam) == IDM_PARAM)
DialogBoxParam(hinst, "DIALOG2" , hwnd,
(DLGPROC)Dialog2Proc, (LPARAM)hwnd);
if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;
return 0;
case WM_SIZE:
MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
/******************************************************************************/
BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK)
{
EndDialog(hDlg,0);
return TRUE;
}
default:
return FALSE;
}
}
/******************************************************************************/
BOOL APIENTRY Dialog2Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
static HWND hParent;
switch (uMsg)
{
case WM_INITDIALOG:
{
CHAR st[256];
hParent = (HWND)lParam;
GetWindowText(hParent, st, 256);
SetDlgItemText(hDlg, IDE_EDIT1, st);
}
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK )
{
CHAR st[256];
GetDlgItemText(hDlg, IDE_EDIT1, st, 256);
SetWindowText(hParent,st);
EndDialog(hDlg,0);
return TRUE;
}
if (LOWORD(wParam) == IDCANCEL )
{
EndDialog(hDlg,0);
return TRUE;
}
default:
return FALSE;
}
}

J'ai testé les compilations avec C++ Builder et DevC++.
Voici un exemple semblable avec le contrôle case à cocher : Le contrôle CheckBox ou case à cocher.

A vos PC.

CGi

Avec la contribution de pharaonix pour la relecture.


21 - Le contrôle CheckBox ou case à cocher.htm

I. Introduction▲

Dans ce chapitre nous allons aborder le traitement des cases à cocher dans les boîtes de dialogue. Vous connaissez la case à cocher, elle sert à refléter un état vrai ou faux, parfois indéterminé. Pour cet exemple nous dessinerons un carré et un cercle sur la fenêtre principale. Ils auront la faculté d'être visibles ou cachés. L'utilisateur pourra changer cet état dans une boîte de dialogue.

Image non disponible

II. Le script de ressources▲

Sélectionnez

DIALOG1 DIALOG
60, 70, 175, 80
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Propriétés"
BEGIN
DEFPUSHBUTTON "OK", IDOK, 36, 52, 42, 12
PUSHBUTTON "Cancel", IDCANCEL, 96, 52, 42, 12
AUTOCHECKBOX "Carré visible ", ID_CB1, 60, 15, 80, 15
AUTOCHECKBOX "Cercle visible", ID_CB2, 60, 30, 80, 15
END

Du point de vue des ressources, il existe deux types de case à cocher la CHECKBOX et l'AUTOCHECKBOX. Dans cet exemple nous choisirons l'AUTOCHECKBOX car elle reflète automatiquement son état quand l'utilisateur clique dessus. Dans l'exemple nous utiliserons deux cases à cocher identifiées par les constantes ID_CB1 et ID_CB2.

III. Passage des paramètres▲

Nous appellerons la boîte de dialogue avec la fonction DialogBox suite à l'action de l'option de menu "propriété" identifié par la constante IDM_PROP. Nous demandons de redessiner la zone client de la fenêtre principale au retour de l'appel de la boîte de dialogue à l'aide de la fonction InvalidateRect :

Sélectionnez

case WM_COMMAND :
if(LOWORD(wParam) == IDM_PROP)
{
DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);
InvalidateRect(hwnd,NULL,TRUE);
}

Nous mémoriserons les propriétés "visible" du carré et du cercle dans une variable globale. Elle aura comme type une structure PROPRIETE préalablement définie :

Sélectionnez

typedef struct
{
BOOL CarreVisible;
BOOL CercleVisible;
} PROPRIETE ;
PROPRIETE prop;
Récupération des données :
switch (uMsg)
{
case WM_INITDIALOG:
{
if(prop.CarreVisible == TRUE)
CheckDlgButton(hDlg, ID_CB1, BST_CHECKED);
if(prop.CercleVisible == TRUE)
CheckDlgButton(hDlg, ID_CB2, BST_CHECKED);
return TRUE;
}

À la réception du message WM_INITDIALOG dans la procédure de fenêtre de la boîte de dialogue, nous testons les valeurs des propriétés du carré et du cercle mémorisées dans la variable prop, puis nous reflétons ces valeurs dans les cases à cocher correspondantes. Ceci à l'aide de la fonction CheckDlgButton. Elle reçoit comme paramètre le handle de la boîte de dialogue, puis l'identificateur de la case à cocher, puis l'état de la case à cocher (BST_CHECKED pour coché et BST_UNCHECKED pour non coché).
Maintenant que notre boîte de dialogue est ouverte, nous pouvons faire notre sélection, puis la fermer.
Si nous la fermons avec le bouton "OK" :

Sélectionnez

case WM_COMMAND :
if (LOWORD(wParam) == IDOK )
{
if(IsDlgButtonChecked(hDlg, ID_CB1) == BST_CHECKED)
prop.CarreVisible = TRUE;
else prop.CarreVisible = FALSE;
if(IsDlgButtonChecked(hDlg, ID_CB2) == BST_CHECKED)
prop.CercleVisible = TRUE;
else prop.CercleVisible = FALSE;
EndDialog(hDlg,0);
return TRUE;
}

Nous faisons l'opération inverse. Nous testons l'état des cases à cocher avec la fonction IsDlgButtonChecked et nous affectons les propriétés du carré et du cercle en conséquence.
IsDlgButtonChecked reçoit comme paramètres le handle de la boîte de dialogue puis la constante d'identification de la case à cocher en question.
Nous devons bien sûr refléter le résultat sur le dessin du carré et du cercle. Ceci sera fait à la réception du message WM_PAINT de la fenêtre principale.

Sélectionnez

case WM_PAINT :
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
if(prop.CarreVisible == TRUE) Rectangle(hdc, 20, 20, 170, 170);
if(prop.CercleVisible == TRUE) Ellipse(hdc, 200, 20, 350, 170);
EndPaint(hwnd, &ps);
return 0;
}

IV. Code complet▲

resource.h :

Sélectionnez

#define IDM_QUIT 1
#define IDM_PROP 2
#define ID_CB1 101
#define ID_CB2 102

resource.rc :

Sélectionnez

#include <windows.h>
#include "resource.h"
LEMENU MENU
BEGIN
POPUP "Fichier"
BEGIN
MENUITEM "Propriétés...", IDM_PROP
MENUITEM SEPARATOR
MENUITEM "Quitter", IDM_QUIT
END
END
DIALOG1 DIALOG
60, 70, 175, 80
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Propriétés"
BEGIN
DEFPUSHBUTTON "OK", IDOK, 36, 52, 42, 12
PUSHBUTTON "Cancel", IDCANCEL, 96, 52, 42, 12
AUTOCHECKBOX "Carré visible ", ID_CB1, 60, 15, 80, 15
AUTOCHECKBOX "Cercle visible", ID_CB2, 60, 30, 80, 15
END

winmain.c :

Sélectionnez

#include <windows.h>
#include "resource.h"
typedef struct
{
BOOL CarreVisible;
BOOL CercleVisible;
} PROPRIETE ;
PROPRIETE prop;
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
hinst = hinstance;
wc.style = 0;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wc.lpszMenuName = "LEMENU";
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "Check Box", WS_OVERLAPPED | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 260,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
prop.CarreVisible = TRUE;
prop.CercleVisible = TRUE;
return 0;
case WM_COMMAND :
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(LOWORD(wParam) == IDM_PROP)
{
DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);
InvalidateRect(hwnd,NULL,TRUE);
}
return 0;
case WM_PAINT :
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
if(prop.CarreVisible == TRUE) Rectangle(hdc, 20, 20, 170, 170);
if(prop.CercleVisible == TRUE) Ellipse(hdc, 200, 20, 350, 170);
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY :
PostQuitMessage(0);
return 0;
default :
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
/******************************************************************************/
BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
{
if(prop.CarreVisible == TRUE)
CheckDlgButton(hDlg, ID_CB1, BST_CHECKED);
if(prop.CercleVisible == TRUE)
CheckDlgButton(hDlg, ID_CB2, BST_CHECKED);
return TRUE;
}
case WM_COMMAND :
if (LOWORD(wParam) == IDOK )
{
if(IsDlgButtonChecked(hDlg, ID_CB1) == BST_CHECKED)
prop.CarreVisible = TRUE;
else prop.CarreVisible = FALSE;
if(IsDlgButtonChecked(hDlg, ID_CB2) == BST_CHECKED)
prop.CercleVisible = TRUE;
else prop.CercleVisible = FALSE;
EndDialog(hDlg,0);
return TRUE;
}
if (LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg,0);
return TRUE;
}
default :
return FALSE;
}
}

J'ai testé les compilations avec C++ Builder et DevC++.

À vos PC.


22 - Le contrôle Radiobutton ou bouton radio_.htm

I. Introduction :▲

Dans ce chapitre nous allons aborder le traitement des boutons radio dans les boîtes de dialogue. Vous connaissez le bouton radio, il sert à faire un choix parmi plusieurs. Pour cet exemple nous dessinerons une figure sur la fenêtre principale qui sera soit un carré soit un cercle soit un triangle. De plus cette figure sera dessinée avec un trait fin, moyen, ou large. L'utilisateur pourra changer ces choix dans une boîte de dialogue.

Image non disponible

II. Le script de ressources :▲

Sélectionnez

DIALOG1 DIALOG

60, 70, 175, 100

STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "Propriétés de la figure"

BEGIN

GROUPBOX " Forme ", -1, 20, 10, 60 , 60, WS_GROUP

AUTORADIOBUTTON "Carré", ID_CARRE, 30, 20, 40, 15,

AUTORADIOBUTTON "Cercle", ID_CERCLE, 30, 35, 40, 15,

AUTORADIOBUTTON "Triangle", ID_TRIANGLE, 30, 50, 40, 15,

GROUPBOX " Trait ", -1, 96, 10, 60 , 60, WS_GROUP

AUTORADIOBUTTON "Fin", ID_RB1, 106, 20, 40, 15

AUTORADIOBUTTON "Moyen", ID_RB2, 106, 35, 40, 15

AUTORADIOBUTTON "Large", ID_RB3, 106, 50, 40, 15

DEFPUSHBUTTON "OK", IDOK, 36, 77, 42, 12, WS_GROUP

PUSHBUTTON "Cancel", IDCANCEL, 96, 77, 42, 12

END

Du point de vue des ressources, il existe deux types de boutons radio le RADIOBUTTON et l'AUTORADIOBUTTON. Dans cet exemple nous choisirons l'AUTORADIOBUTTON car il reflète automatiquement son état quand l'utilisateur clique dessus et il désélectionne automatiquement les autres boutons radio de type AUTORADIOBUTTON du même groupe que lui.

Pour former des groupes il faut donner le style WS_GROUP au premier contrôle de chaque groupe. Si aucun contrôle n'a le style WS_GROUP, ils font donc parti du même groupe.

III. Passage des paramètres :▲

Nous appellerons la boîte de dialogue avec la fonction DialogBox suite à l'action de l'option de menu "propriété..." identifié par la constante IDM_PROP. Nous demandons de redessiner la zone client de la fenêtre principale au retour de l'appel de la boîte de dialogue à l'aide de la fonction InvalidateRect. Elle va envoyer un message WM_PAINT vers la fenêtre principale, afin de dessiner la nouvelle figure.

Sélectionnez

case WM_COMMAND:

if(LOWORD(wParam) == IDM_PROP)

{

if(DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc)

== DB_OK)

InvalidateRect(hwnd,NULL,TRUE);

}

Nous mémoriserons les propriétés dans des variables globales. Leurs valeurs seront nommées à l'aide de constantes Pour la taille du trait la valeur de la constante sera la largeur du trait. Pour la forme de la figure nous utiliserons les mêmes constantes que celles des boutons radio qui leurs correspondent.

Sélectionnez

#define FIN 1

#define MOYEN 2

#define LARGE 4

UINT Forme;

UINT Trait;

Récupération des données :

Sélectionnez

switch (uMsg)

{

case WM_INITDIALOG:

{

if(Trait == FIN ) CheckDlgButton(hDlg, ID_RB1, BST_CHECKED);

if(Trait == MOYEN) CheckDlgButton(hDlg, ID_RB2, BST_CHECKED);

if(Trait == LARGE) CheckDlgButton(hDlg, ID_RB3, BST_CHECKED);

CheckRadioButton(hDlg, ID_CARRE, ID_TRIANGLE, Forme);

return TRUE;

}

A la réception du message WM_INITDIALOG dans la procédure de fenêtre de la boîte de dialogue, nous testons les valeurs des propriétés de la figure, puis nous reflétons ces valeurs dans les cases les boutons radios correspondants. Pour le groupe "Trait" nous utiliserons comme pour les cases à cocher la fonction CheckDlgButton. Elle reçoit comme paramètres le handle de la boîte de dialogue, puis l'identificateur du bouton radio, puis la valeur BST_CHECKED s'il doit être sélectionné.

Pour le groupe "Forme" nous utiliserons la fonction CheckRadioButton. Elle reçoit comme paramètres le handle de la boîte de dialogue, puis le premier identificateur du groupe, puis le dernier identificateur du groupe, puis l'identificateur du bouton radio à sélectionner. Elle a l'avantage par rapport à CheckDlgButton à sélectionner le bouton radio en question et à désélectionner les autres du groupe. Ce qui n'est pas le cas de CheckDlgButton. Maintenant que notre boîte de dialogue est ouverte, nous pouvons faire notre sélection, puis la fermer.

Si nous la fermons avec le bouton "Ok" :

Sélectionnez

case WM_COMMAND:

if (LOWORD(wParam) == IDOK )

{

if(IsDlgButtonChecked(hDlg, ID_RB1) == BST_CHECKED)

Trait = FIN;

if(IsDlgButtonChecked(hDlg, ID_RB2) == BST_CHECKED)

Trait = MOYEN;

if(IsDlgButtonChecked(hDlg, ID_RB3) == BST_CHECKED)

Trait = LARGE;

if(IsDlgButtonChecked(hDlg, ID_CARRE) == BST_CHECKED)

Forme = ID_CARRE;

if(IsDlgButtonChecked(hDlg, ID_CERCLE) == BST_CHECKED)

Forme = ID_CERCLE;

if(IsDlgButtonChecked(hDlg, ID_TRIANGLE) == BST_CHECKED)

Forme = ID_TRIANGLE;

EndDialog(hDlg,DB_OK);

return TRUE;

}

Nous faisons l'opération inverse. Nous testons l'état des boutons radio avec la fonction IsDlgButtonChecked et nous affectons les propriétés de la figure en conséquence.

IsDlgButtonChecked reçoit comme paramètres le handle de la boîte de dialogue puis la constante d'identification de la case à cocher en question.

Nous devons bien sûr refléter le résultat sur le dessin de la figure. Ceci sera fait à la réception du message WM_PAINT de la fenêtre principale.

Sélectionnez

case WM_PAINT :

{

PAINTSTRUCT ps;

HDC hdc ;

HPEN hpen, hpOld;

POINT ptTriangle[3];

ptTriangle[0].x = 20;

ptTriangle[0].y = 20;

ptTriangle[1].x = 20;

ptTriangle[1].y = 170;

ptTriangle[2].x = 170;

ptTriangle[2].y = 95;

hdc = BeginPaint(hwnd, &ps);

hpen = CreatePen(PS_SOLID, Trait, 0);

hpOld = SelectObject(hdc,hpen);

if(Forme == ID_CARRE) Rectangle(hdc, 20, 20, 170, 170);

if(Forme == ID_CERCLE) Ellipse(hdc, 20, 20, 170, 170);

if(Forme == ID_TRIANGLE) Polygon(hdc, ptTriangle, 3);

SelectObject(hdc,hpOld);

DeleteObject(hpen);

EndPaint(hwnd, &ps);

return 0;

}

IV. Code complet :▲

IV-A. resource.h :▲

Sélectionnez

#define IDM_QUIT 1

#define IDM_PROP 2

#define ID_RB1 101

#define ID_RB2 102

#define ID_RB3 103

#define ID_CARRE 104

#define ID_CERCLE 105

#define ID_TRIANGLE 106

IV-B. resource.rc :▲

Sélectionnez

#include <windows.h>

#include "resource.h"

LEMENU MENU

BEGIN

POPUP "Fichier"

BEGIN

MENUITEM "Propriétés...", IDM_PROP

MENUITEM SEPARATOR

MENUITEM "Quitter", IDM_QUIT

END

END

DIALOG1 DIALOG

60, 70, 175, 100

STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "Propriétés de la figure"

BEGIN

GROUPBOX " Forme ", -1, 20, 10, 60 , 60, WS_GROUP

AUTORADIOBUTTON "Carré", ID_CARRE, 30, 20, 40, 15

AUTORADIOBUTTON "Cercle", ID_CERCLE, 30, 35, 40, 15

AUTORADIOBUTTON "Triangle", ID_TRIANGLE, 30, 50, 40, 15

GROUPBOX " Trait ", -1, 96, 10, 60 , 60, WS_GROUP

AUTORADIOBUTTON "Fin", ID_RB1, 106, 20, 40, 15

AUTORADIOBUTTON "Moyen", ID_RB2, 106, 35, 40, 15

AUTORADIOBUTTON "Large", ID_RB3, 106, 50, 40, 15

DEFPUSHBUTTON "OK", IDOK, 36, 77, 42, 12, WS_GROUP

PUSHBUTTON "Cancel", IDCANCEL, 96, 77, 42, 12

END

IV-C. winmain.c :▲

Sélectionnez

#include <windows.h>

#include "resource.h"

#define DB_OK 1

#define FIN 1

#define MOYEN 2

#define LARGE 4

UINT Forme;

UINT Trait;

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

HWND hwnd;

MSG msg;

WNDCLASS wc;

hinst = hinstance;

wc.style = 0;

wc.lpfnWndProc = MainWndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hinstance;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);

wc.lpszMenuName = "LEMENU";

wc.lpszClassName = "MaWinClass";

if(!RegisterClass(&wc)) return FALSE;

hwnd = CreateWindow("MaWinClass", "Check Box", WS_OVERLAPPED | WS_SYSMENU,

CW_USEDEFAULT, CW_USEDEFAULT, 400, 260,

NULL, NULL, hinstance, NULL);

if (!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow);

UpdateWindow(hwnd);

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

switch (uMsg)

{

case WM_CREATE:

Forme = ID_CARRE;

Trait = FIN;

return 0;

case WM_COMMAND:

if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);

if(LOWORD(wParam) == IDM_PROP)

{

if(DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc)

== DB_OK)

InvalidateRect(hwnd,NULL,TRUE);

}

return 0;

case WM_PAINT :

{

PAINTSTRUCT ps;

HDC hdc ;

HPEN hpen, hpOld;

POINT ptTriangle[3];

ptTriangle[0].x = 20;

ptTriangle[0].y = 20;

ptTriangle[1].x = 20;

ptTriangle[1].y = 170;

ptTriangle[2].x = 170;

ptTriangle[2].y = 95;

hdc = BeginPaint(hwnd, &ps);

hpen = CreatePen(PS_SOLID, Trait, 0);

hpOld = SelectObject(hdc,hpen);

if(Forme == ID_CARRE) Rectangle(hdc, 20, 20, 170, 170);

if(Forme == ID_CERCLE) Ellipse(hdc, 20, 20, 170, 170);

if(Forme == ID_TRIANGLE) Polygon(hdc, ptTriangle, 3);

SelectObject(hdc,hpOld);

DeleteObject(hpen);

EndPaint(hwnd, &ps);

return 0;

}

case WM_DESTROY:

PostQuitMessage(0);

return 0;

default:

return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

}

/*****************************************************************************/

BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)

{

switch (uMsg)

{

case WM_INITDIALOG:

{

if(Trait == FIN ) CheckDlgButton(hDlg, ID_RB1, BST_CHECKED);

if(Trait == MOYEN) CheckDlgButton(hDlg, ID_RB2, BST_CHECKED);

if(Trait == LARGE) CheckDlgButton(hDlg, ID_RB3, BST_CHECKED);

CheckRadioButton(hDlg, ID_CARRE, ID_TRIANGLE, Forme);

return TRUE;

}

case WM_COMMAND:

if (LOWORD(wParam) == IDOK )

{

if(IsDlgButtonChecked(hDlg, ID_RB1) == BST_CHECKED)

Trait = FIN;

if(IsDlgButtonChecked(hDlg, ID_RB2) == BST_CHECKED)

Trait = MOYEN;

if(IsDlgButtonChecked(hDlg, ID_RB3) == BST_CHECKED)

Trait = LARGE;

if(IsDlgButtonChecked(hDlg, ID_CARRE) == BST_CHECKED)

Forme = ID_CARRE;

if(IsDlgButtonChecked(hDlg, ID_CERCLE) == BST_CHECKED)

Forme = ID_CERCLE;

if(IsDlgButtonChecked(hDlg, ID_TRIANGLE) == BST_CHECKED)

Forme = ID_TRIANGLE;

EndDialog(hDlg,DB_OK);

return TRUE;

}

if (LOWORD(wParam) == IDCANCEL)

{

EndDialog(hDlg,0);

return TRUE;

}

default:

return FALSE;

}

}

J'ai testé les compilations avec C++ Builder et DevC++.

A vos PC.


23 - Le contrôle ListBox ou liste de choix.htm

I. Introduction▲

Vous connaissez la liste de choix, appelée aussi listbox. Elle sert à faire un choix, parfois plusieurs parmi une liste.
Afin de l'implémenter, nous reprendrons l'exemple des boutons radio où nous remplacerons dans la boîte de dialogue le groupe de boutons radio nommé "figure" par une liste de choix.

Image non disponible

II. Mise en uvre▲

Le contrôle liste de choix sera ajouté depuis les ressources et identifié par la constante ID_LB1 :

Sélectionnez

LISTBOX ID_LB1, 20, 24, 56, 45

Comme pour l'exemple des boutons radio, les propriétés seront mémorisées dans des variables globales. Les valeurs de la propriété figure seront définis à l'aide de constantes et mémorisées dans la variable Forme.

Sélectionnez

#define ID_CARRE 0
#define ID_CERCLE 1
#define ID_TRIANGLE 2
UINT Forme;

Initialisation des données :

Sélectionnez

switch (uMsg)
{
case WM_INITDIALOG:
{
HWND hwndList = GetDlgItem(hDlg, ID_LB1);
SendMessage(hwndList, LB_ADDSTRING, 0, (LONG)"Carré");
SendMessage(hwndList, LB_ADDSTRING, 0, (LONG)"Cercle");
SendMessage(hwndList, LB_ADDSTRING, 0, (LONG)"Triangle");
SendMessage(hwndList, LB_SETCURSEL, Forme, 0);
return TRUE;
}

Pour initialiser la liste de choix, nous récupérons tout d'abord son handle de fenêtre avec la fonction GetDlgItem. Nous remplissons ensuite la liste avec des chaînes de caractères représentant les divers choix possibles. Ceci en envoyant à la liste, des messages LB_ADDSTRING avec un pointeur sur la chaîne en question comme paramètre lParam du message. Puis nous sélectionnons l'option de la liste de choix correspondant à la figure actuelle en lui envoyant un message LB_SETCURSEL avec l'index de la sélection actuelle comme paramètre wParam du message (index mémorisé dans la variable globale Forme). Les index dans les listes de choix commencent par la valeur 0.

Récupération du choix :

Sélectionnez

case WM_COMMAND :
if (LOWORD(wParam) == IDOK )
{
Forme = SendDlgItemMessage(hDlg, ID_LB1, LB_GETCURSEL, 0, 0);
EndDialog(hDlg,DB_OK);
return TRUE;
}

Pour récupérer la sélection que l'utilisateur a choisie dans la liste de choix, nous lui envoyons un message LB_GETCURSEL avec la fonction SendDlgItemMessage, la valeur de l'index étant retournée par cette fonction.
SendDlgItemMessage a la même fonction que SendMessage sauf qu'elle permet d'envoyer un message à un contrôle sans connaître son handle de fenêtre. Elle reçoit à la place le handle de la boîte de dialogue et l'identificateur du contrôle. Nous aurions pu aussi l'utiliser pour initialiser la liste de choix.
Nous traiterons la notification LBN_DBLCLK, envoyée à la boîte de dialogue quand l'utilisateur fait un double-clic sur une option de la liste de choix. Ce traitement sera la fermeture de la boîte de dialogue :

Sélectionnez

case WM_COMMAND :
if (LOWORD(wParam) == IDOK || HIWORD(wParam) == LBN_DBLCLK)
{
Forme = SendDlgItemMessage(hDlg, ID_LB1, LB_GETCURSEL, 0, 0);
EndDialog(hDlg,DB_OK);
return TRUE;
}

Le reste du code est identique à celui de l'exemple des boutons radio .

III. Code complet▲

resource.h

Sélectionnez

#define IDM_QUIT 1
#define IDM_PROP 2
#define ID_RB1 101
#define ID_RB2 102
#define ID_RB3 103
#define ID_LB1 201

resource.rc

Sélectionnez

#include <windows.h>
#include "resource.h"
LEMENU MENU
BEGIN
POPUP "Fichier"
BEGIN
MENUITEM "Propriétés...", IDM_PROP
MENUITEM SEPARATOR
MENUITEM "Quitter", IDM_QUIT
END
END
DIALOG1 DIALOG
110, 0, 175, 100
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Propriétés de la figure"
BEGIN
LTEXT "Figure", -1, 35, 10, 30, 10, WS_GROUP
LISTBOX ID_LB1, 20, 24, 56, 45
GROUPBOX " Trait ", -1, 96, 10, 60 , 60, WS_GROUP
AUTORADIOBUTTON "Fin", ID_RB1, 106, 20, 40, 15
AUTORADIOBUTTON "Moyen", ID_RB2, 106, 35, 40, 15
AUTORADIOBUTTON "Large", ID_RB3, 106, 50, 40, 15
DEFPUSHBUTTON "OK", IDOK, 36, 77, 42, 12, WS_GROUP
PUSHBUTTON "Cancel", IDCANCEL, 96, 77, 42, 12
END

winmain.c

Sélectionnez

#include <windows.h>
#include "resource.h"
#define DB_OK 1
#define FIN 1
#define MOYEN 2
#define LARGE 8
#define ID_CARRE 0
#define ID_CERCLE 1
#define ID_TRIANGLE 2
UINT Forme;
UINT Trait;
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
hinst = hinstance;
wc.style = 0;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wc.lpszMenuName = "LEMENU";
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "Check Box", WS_OVERLAPPED | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 260,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
Forme = ID_CARRE;
Trait = FIN;
return 0;
case WM_COMMAND :
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(LOWORD(wParam) == IDM_PROP)
{
if(DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc)
== DB_OK)
InvalidateRect(hwnd,NULL,TRUE);
}
return 0;
case WM_PAINT :
{
PAINTSTRUCT ps;
HDC hdc ;
HPEN hpen, hpOld;
POINT ptTriangle[3];
ptTriangle[0].x = 20;
ptTriangle[0].y = 20;
ptTriangle[1].x = 20;
ptTriangle[1].y = 170;
ptTriangle[2].x = 170;
ptTriangle[2].y = 95;
hdc = BeginPaint(hwnd, &ps);
hpen = CreatePen(PS_SOLID, Trait, 0);
hpOld = SelectObject(hdc,hpen);
if(Forme == ID_CARRE) Rectangle(hdc, 20, 20, 170, 170);
if(Forme == ID_CERCLE) Ellipse(hdc, 20, 20, 170, 170);
if(Forme == ID_TRIANGLE) Polygon(hdc, ptTriangle, 3);
SelectObject(hdc,hpOld);
DeleteObject(hpen);
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY :
PostQuitMessage(0);
return 0;
default :
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
/******************************************************************************/
BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
{
HWND hwndList = GetDlgItem(hDlg, ID_LB1);
SendMessage(hwndList, LB_ADDSTRING, 0, (LONG)"Carré");
SendMessage(hwndList, LB_ADDSTRING, 0, (LONG)"Cercle");
SendMessage(hwndList, LB_ADDSTRING, 0, (LONG)"Triangle");
SendMessage(hwndList, LB_SETCURSEL, Forme, 0);
if(Trait == FIN ) CheckDlgButton(hDlg, ID_RB1, BST_CHECKED);
if(Trait == MOYEN) CheckDlgButton(hDlg, ID_RB2, BST_CHECKED);
if(Trait == LARGE) CheckDlgButton(hDlg, ID_RB3, BST_CHECKED);
return TRUE;
}
case WM_COMMAND :
if (LOWORD(wParam) == IDOK || HIWORD(wParam) == LBN_DBLCLK)
{
if(IsDlgButtonChecked(hDlg, ID_RB1) == BST_CHECKED)
Trait = FIN;
if(IsDlgButtonChecked(hDlg, ID_RB2) == BST_CHECKED)
Trait = MOYEN;
if(IsDlgButtonChecked(hDlg, ID_RB3) == BST_CHECKED)
Trait = LARGE;
Forme = SendDlgItemMessage(hDlg, ID_LB1, LB_GETCURSEL , 0, 0);
EndDialog(hDlg,DB_OK);
return TRUE;
}
if (LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg,0);
return TRUE;
}
default :
return FALSE;
}
}

La liste de choix peut traiter de nombreux messages qui lui sont spécifiques. Je vous invite à consulter l'aide sur l'API Win32.
J'ai testé les compilations avec C++ Builder et DevC++.

À vos PC.


24 - Le contrôle ComboBox ou liste déroulante.htm

I. Introduction▲

Vous connaissez la liste déroulante, appelée aussi Combobox. C'est un champ d'édition associé à une liste de choix. Comme la liste de choix, elle sert à faire un choix parmi plusieurs, et selon son style elle peut être éditable ou non.
Afin de l'implémenter, nous reprendrons l'exemple de la liste de choix , ou nous la remplacerons par une liste déroulante.

Image non disponible

II. Mise en uvre▲

Le contrôle liste déroulante sera ajouté depuis les ressources et identifié par la constante ID_CB1. Elle aura le style CBS_DROPDOWNLIST (déroulante non éditable).

Sélectionnez

COMBOBOX ID_CB1, 20, 24, 56, 45, CBS_DROPDOWNLIST

Comme pour l'exemple de la liste de choix, les valeurs de la propriété figure seront définies à l'aide de constante et mémorisées dans la variable Forme.

Sélectionnez

#define ID_CARRE 0
#define ID_CERCLE 1
#define ID_TRIANGLE 2
UINT Forme;
Initialisation des données :
switch (uMsg)
{
case WM_INITDIALOG:
{
SendDlgItemMessage(hDlg, ID_CB1, CB_ADDSTRING, 0, (LONG)"Carré");
SendDlgItemMessage(hDlg, ID_CB1, CB_ADDSTRING, 0, (LONG)"Cercle");
SendDlgItemMessage(hDlg, ID_CB1, CB_ADDSTRING, 0, (LONG)"Triangle");
SendDlgItemMessage(hDlg, ID_CB1, CB_SETCURSEL, Forme, 0);
return TRUE;
}

Nous remplissons la liste déroulante avec les chaînes de caractères représentant les divers choix possibles. Ceci en envoyant à la liste déroulante, des messages CB_ADDSTRING avec un pointeur sur la chaîne en question comme paramètre lParam du message. Les messages seront envoyés avec la fonction SendDlgItemMessage. Puis nous sélectionnons l'option de la liste déroulante correspondant à la figure actuelle en lui envoyant un message CB_SETCURSEL avec l'index de la sélection actuelle comme paramètre wParam du message (index mémorisé dans la variable globale Forme). Les index dans les listes déroulantes commençant par la valeur 0.

Récupération du choix :

Sélectionnez

case WM_COMMAND :
if (LOWORD(wParam) == IDOK )
{
Forme = SendDlgItemMessage(hDlg, ID_CB1, CB_GETCURSEL, 0, 0);
EndDialog(hDlg,DB_OK);
return TRUE;
}

Pour récupérer la sélection que l'utilisateur a choisie dans la liste déroulante nous lui envoyons un message CB_GETCURSEL avec la fonction SendDlgItemMessage, la valeur de l'index étant retournée par cette fonction.

Le reste du code est identique à l'exemple de la liste de choix .

III. Code complet▲

resource.h :

Sélectionnez

#define IDM_QUIT 1
#define IDM_PROP 2
#define ID_RB1 101
#define ID_RB2 102
#define ID_RB3 103
#define ID_CB1 201

resource.rc :

Sélectionnez

#include <windows.h>
#include "resource.h"
LEMENU MENU
BEGIN
POPUP "Fichier"
BEGIN
MENUITEM "Propriétés...", IDM_PROP
MENUITEM SEPARATOR
MENUITEM "Quitter", IDM_QUIT
END
END
DIALOG1 DIALOG
110, 0, 175, 100
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Propriétés de la figure"
BEGIN
LTEXT "Figure", -1, 35, 10, 30, 10 WS_GROUP
COMBOBOX ID_CB1, 20, 24, 56, 45, CBS_DROPDOWNLIST
GROUPBOX " Trait ", -1, 96, 10, 60 , 60, WS_GROUP
AUTORADIOBUTTON "Fin", ID_RB1, 106, 20, 40, 15
AUTORADIOBUTTON "Moyen", ID_RB2, 106, 35, 40, 15
AUTORADIOBUTTON "Large", ID_RB3, 106, 50, 40, 15
DEFPUSHBUTTON "OK", IDOK, 36, 77, 42, 12, WS_GROUP
PUSHBUTTON "Cancel", IDCANCEL, 96, 77, 42, 12
END

winmain.c :

Sélectionnez

#include <windows.h>
#include "resource.h"
#define DB_OK 1
#define FIN 1
#define MOYEN 2
#define LARGE 8
#define ID_CARRE 0
#define ID_CERCLE 1
#define ID_TRIANGLE 2
UINT Forme;
UINT Trait;
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
hinst = hinstance;
wc.style = 0;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wc.lpszMenuName = "LEMENU";
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "Check Box", WS_OVERLAPPED | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 260,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
Forme = ID_CARRE;
Trait = FIN;
return 0;
case WM_COMMAND :
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(LOWORD(wParam) == IDM_PROP)
{
if(DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc)
== DB_OK)
InvalidateRect(hwnd,NULL,TRUE);
}
return 0;
case WM_PAINT :
{
PAINTSTRUCT ps;
HDC hdc ;
HPEN hpen, hpOld;
POINT ptTriangle[3];
ptTriangle[0].x = 20;
ptTriangle[0].y = 20;
ptTriangle[1].x = 20;
ptTriangle[1].y = 170;
ptTriangle[2].x = 170;
ptTriangle[2].y = 95;
hdc = BeginPaint(hwnd, &ps);
hpen = CreatePen(PS_SOLID, Trait, 0);
hpOld = SelectObject(hdc,hpen);
if(Forme == ID_CARRE) Rectangle(hdc, 20, 20, 170, 170);
if(Forme == ID_CERCLE) Ellipse(hdc, 20, 20, 170, 170);
if(Forme == ID_TRIANGLE) Polygon(hdc, ptTriangle, 3);
SelectObject(hdc,hpOld);
DeleteObject(hpen);
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY :
PostQuitMessage(0);
return 0;
default :
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
/******************************************************************************/
BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
{
SendDlgItemMessage(hDlg, ID_CB1, CB_ADDSTRING, 0, (LONG)"Carré");
SendDlgItemMessage(hDlg, ID_CB1, CB_ADDSTRING, 0, (LONG)"Cercle");
SendDlgItemMessage(hDlg, ID_CB1, CB_ADDSTRING, 0, (LONG)"Triangle");
SendDlgItemMessage(hDlg, ID_CB1, CB_SETCURSEL, Forme, 0);
if(Trait == FIN ) CheckDlgButton(hDlg, ID_RB1, BST_CHECKED);
if(Trait == MOYEN) CheckDlgButton(hDlg, ID_RB2, BST_CHECKED);
if(Trait == LARGE) CheckDlgButton(hDlg, ID_RB3, BST_CHECKED);
return TRUE;
}
case WM_COMMAND :
if (LOWORD(wParam) == IDOK)
{
if(IsDlgButtonChecked(hDlg, ID_RB1) == BST_CHECKED)
Trait = FIN;
if(IsDlgButtonChecked(hDlg, ID_RB2) == BST_CHECKED)
Trait = MOYEN;
if(IsDlgButtonChecked(hDlg, ID_RB3) == BST_CHECKED)
Trait = LARGE;
Forme = SendDlgItemMessage(hDlg, ID_CB1, CB_GETCURSEL, 0, 0);
EndDialog(hDlg,DB_OK);
return TRUE;
}
if (LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg,0);
return TRUE;
}
default :
return FALSE;
}
}

La liste déroulante peut traiter de nombreux messages qui lui sont spécifiques. Je vous invite à consulter l'aide sur l'API Win32.
J'ai testé les compilations avec C++ Builder et DevC++.

À vos PC.


25 - Le contrôle scroll bar ou ascenseur_.htm

I. Introduction :▲

Nous allons voir l'ascenseur en tant que contrôle. Je parle de celui que l'on utilise pour changer des valeurs et non des ascenseurs associés à certain contrôle pour en faire défiler leur contenu.

Afin de l'implémenter, nous reprendrons l'exemple de la liste déroulante , ou nous remplacerons le groupe de boutons radio destiné à modifier la largeur du trait par un ascenseur horizontal.

Image non disponible

II. Mise en oeuvre :▲

L'ascenseur sera ajouté depuis les ressources et identifié par la constante ID_SB1.

Sélectionnez

SCROLLBAR ID_SB1, 96, 24, 60, 10

Comme pour l'exemple de la liste déroulante la largeur du trait sera mémorisée dans la variable globale Trait.

Pour utiliser un ascenseur, nous devons définir une structure SCROLLINFO servant de tampon pour transmettre ou récupérer les données de l'ascenseur.

Initialisation des données :

Sélectionnez

static HWND hsb;

static SCROLLINFO si;

switch (uMsg)

{

case WM_INITDIALOG:

{

hsb = GetDlgItem(hDlg, ID_SB1);

ZeroMemory(&si, sizeof(SCROLLINFO));

si.cbSize = sizeof(si);

si.fMask = SIF_RANGE | SIF_POS;

si.nMin = 0;

si.nMax = 10;

si.nPos = Trait;

SetScrollInfo(hsb, SB_CTL, &si, FALSE);

SetDlgItemInt(hDlg, ID_LT1, Trait, FALSE);

return TRUE;

}

A l'initialisation de la boîte de dialogue nous remplissons la structure SCROLLINFO, avec le champ fMask initialisé selon l'action que l'on voudra faire, puis le champ nMin avec la valeur mini, le champs nMax avec la valeur maxi et le champ nPos avec la largeur du trait actuelle afin qu'il reflète sa taille sur l'ascenseur. Nous exécutons le transfère des données avec la fonction SetScrollInfo qui reçoit comme paramètre : le handle de fenêtre de l'ascenseur, puis le flag SB_CTL car c'est un contrôle, puis l'adresse de la variable (SCROLLINFO)si. Le dernier paramètre indique si l'on veut redessiner la barre à l'appel de la fonction.

J'ai rajouté dans la boîte de dialogue un contrôle texte pour afficher la valeur de la largeur du trait. Il est mis à jour avec la fonction SetDlgItemInt qui éffectue automatiquement la conversion d'un entier en texte.

Ce contrôle n'est pas le plus simple car nous devons gérer par programme son positionnement. Quand la position d'un ascenseur est modifié un message WM_HSCROLL est envoyé à sa fenêtre parent. C'est donc sur ce message que l'on va gérer l'ascenseur.

Sélectionnez

case WM_HSCROLL:

GetScrollInfo((HWND)lParam, SB_CTL, &si);

switch (LOWORD(wParam))

{

case SB_PAGEUP:

si.nPos -= 2;

if(si.nPos < si.nMin) si.nPos = si.nMin ;

break;

case SB_PAGEDOWN:

si.nPos += 2;

if(si.nPos > si.nMax) si.nPos = si.nMax ;

break;

case SB_LINEUP:

if(si.nPos > si.nMin) si.nPos--;

break;

case SB_LINEDOWN:

if(si.nPos < si.nMax) si.nPos++;

break;

case SB_THUMBPOSITION:

si.nPos = HIWORD(wParam);

break;

}

si.fMask = SIF_POS;

SetScrollInfo((HWND)lParam, SB_CTL, &si, TRUE);

SetDlgItemInt(hDlg, ID_LT1, si.nPos, FALSE);

return TRUE;

Nous récupérons les informations de l'ascenseur en cours de manipulation dans la variable (SCROLLINFO)si, avec la fonction GetScrollInfo (il peut y avoir plusieurs ascenseurs). Le handle de fenêtre de l'ascenseur étant récupéré dans le paramètre lParam du message. Dans le mot de poids faible de son paramètre wParam, il retourne l'action qui a été effectué sur l'ascenseur.

  • SB_PAGEUP : Click sur la zone entre la flèche de gauche ou du haut et le curseur.
  • SB_PAGEDOWN : Click sur la zone entre le curseur et la flèche de droite ou du bas.
  • SB_LINEUP : Click sur la flèche de gauche ou du haut s'il est vertical.
  • SB_LINEDOWN : Click sur la flèche de droite ou du bas s'il est vertical.
  • SB_THUMBPOSITION : déplacement du curseur. En ce cas la valeur est retournée par le mot de poids fort du paramètre wParam.

Nous nous en servons pour changer ses positions qui seront effectives après l'appel de la fonction SetScrollInfo. Nous mettons de même à jour le texte reflétant sa valeur avec la fonction SetDlgItemInt.

A la fermeture de la boîte de dialogue par Ok :

Sélectionnez

case WM_COMMAND:

if (LOWORD(wParam) == IDOK)

{

GetScrollInfo(hsb, SB_CTL, &si);

Trait = si.nPos;

EndDialog(hDlg,DB_OK);

return TRUE;

}

Nous récupérons sa position à l'aide de GetScrollInfo et nous l'affectons à la variable Trait.

III. Code complet :▲

III-A. resource.h :▲

Sélectionnez

#define IDM_QUIT 1

#define IDM_PROP 2

#define ID_SB1 101

#define ID_CB1 102

#define ID_LT1 103

III-B. resource.rc :▲

Sélectionnez

#include <windows.h>

#include "resource.h"

LEMENU MENU

BEGIN

POPUP "Fichier"

BEGIN

MENUITEM "Propriétés...", IDM_PROP

MENUITEM SEPARATOR

MENUITEM "Quitter", IDM_QUIT

END

END

DIALOG1 DIALOG

110, 0, 175, 90

STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "Propriétés de la figure"

BEGIN

LTEXT "Figure", -1, 35, 10, 30, 10

COMBOBOX ID_CB1, 20, 24, 56, 45, CBS_DROPDOWNLIST

LTEXT "Trait", -1, 115, 10, 30, 10

SCROLLBAR ID_SB1, 96, 24, 60, 10

LTEXT "Larg :", -1, 110, 44, 20, 10

RTEXT "0", ID_LT1, 130, 44, 10, 10

DEFPUSHBUTTON "OK", IDOK, 36, 65, 42, 12

PUSHBUTTON "Cancel", IDCANCEL, 96, 65, 42, 12

END

III-C. winmain.c :▲

Sélectionnez

#include <windows.h>

#include "resource.h"

#define DB_OK 1

#define ID_CARRE 0

#define ID_CERCLE 1

#define ID_TRIANGLE 2

UINT Forme;

UINT Trait;

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

HWND hwnd;

MSG msg;

WNDCLASS wc;

hinst = hinstance;

wc.style = 0;

wc.lpfnWndProc = MainWndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hinstance;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);

wc.lpszMenuName = "LEMENU";

wc.lpszClassName = "MaWinClass";

if(!RegisterClass(&wc)) return FALSE;

hwnd = CreateWindow("MaWinClass", "Check Box", WS_OVERLAPPED | WS_SYSMENU,

CW_USEDEFAULT, CW_USEDEFAULT, 400, 260,

NULL, NULL, hinstance, NULL);

if (!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow);

UpdateWindow(hwnd);

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

switch (uMsg)

{

case WM_CREATE:

Forme = ID_CARRE;

Trait = 1;

return 0;

case WM_COMMAND:

if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);

if(LOWORD(wParam) == IDM_PROP)

{

if(DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc)

== DB_OK)

InvalidateRect(hwnd,NULL,TRUE);

}

return 0;

case WM_PAINT :

{

PAINTSTRUCT ps;

HDC hdc ;

HPEN hpen, hpOld;

POINT ptTriangle[3];

ptTriangle[0].x = 20;

ptTriangle[0].y = 20;

ptTriangle[1].x = 20;

ptTriangle[1].y = 170;

ptTriangle[2].x = 170;

ptTriangle[2].y = 95;

hdc = BeginPaint(hwnd, &ps);

hpen = CreatePen(PS_SOLID, Trait, 0);

hpOld = SelectObject(hdc,hpen);

if(Forme == ID_CARRE) Rectangle(hdc, 20, 20, 170, 170);

if(Forme == ID_CERCLE) Ellipse(hdc, 20, 20, 170, 170);

if(Forme == ID_TRIANGLE) Polygon(hdc, ptTriangle, 3);

SelectObject(hdc,hpOld);

DeleteObject(hpen);

EndPaint(hwnd, &ps);

return 0;

}

case WM_DESTROY:

PostQuitMessage(0);

return 0;

default:

return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

}

/******************************************************************************/

BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)

{

static HWND hsb;

static SCROLLINFO si;

switch (uMsg)

{

case WM_INITDIALOG:

{

SendDlgItemMessage(hDlg, ID_CB1, CB_ADDSTRING, 0, (LONG)"Carré");

SendDlgItemMessage(hDlg, ID_CB1, CB_ADDSTRING, 0, (LONG)"Cercle");

SendDlgItemMessage(hDlg, ID_CB1, CB_ADDSTRING, 0, (LONG)"Triangle");

SendDlgItemMessage(hDlg, ID_CB1, CB_SETCURSEL, Forme, 0);

hsb = GetDlgItem(hDlg, ID_SB1);

ZeroMemory(&si, sizeof(SCROLLINFO));

si.cbSize = sizeof(si);

si.fMask = SIF_RANGE | SIF_POS;

si.nMin = 0;

si.nMax = 10;

si.nPos = Trait;

SetScrollInfo(hsb, SB_CTL, &si, FALSE);

SetDlgItemInt(hDlg, ID_LT1, Trait, FALSE);

return TRUE;

}

case WM_HSCROLL:

GetScrollInfo((HWND)lParam, SB_CTL, &si);

switch (LOWORD(wParam))

{

case SB_PAGEUP:

si.nPos -= 2;

if(si.nPos < si.nMin) si.nPos = si.nMin ;

break;

case SB_PAGEDOWN:

si.nPos += 2;

if(si.nPos > si.nMax) si.nPos = si.nMax ;

break;

case SB_LINEUP:

if(si.nPos > si.nMin) si.nPos--;

break;

case SB_LINEDOWN:

if(si.nPos < si.nMax) si.nPos++;

break;

case SB_THUMBPOSITION:

si.nPos = HIWORD(wParam);

break;

}

si.fMask = SIF_POS;

SetScrollInfo((HWND)lParam, SB_CTL, &si, TRUE);

SetDlgItemInt(hDlg, ID_LT1, si.nPos, FALSE);

return TRUE;

case WM_COMMAND:

if (LOWORD(wParam) == IDOK)

{

GetScrollInfo(hsb, SB_CTL, &si);

Trait = si.nPos;

Forme = SendDlgItemMessage(hDlg, ID_CB1, CB_GETCURSEL, 0, 0);

EndDialog(hDlg,DB_OK);

return TRUE;

}

if (LOWORD(wParam) == IDCANCEL)

{

EndDialog(hDlg,0);

return TRUE;

}

default:

return FALSE;

}

}

J'ai testé les compilations avec C++ Builder et DevC++.

A vos PC.


26 - Le contrôle Static_.htm

I. Introduction :▲

Un petit passage pour voir le contrôle static. En fait nous l'avons déjà utilisé dans plusieurs exemples. C'est lui qui nous affichait les textes dans les boîtes de dialogue. C'est lui aussi qui nous affichait l'icône dans la boîte de dialogue du chapitre "Les boîtes de dialogue personnalisées" . Le nom de sa classe de fenêtre est "STATIC".

Dans cette exemple nous utiliserons 3 contrôles static dans une boîte de dialogue, un pour afficher un texte, un autre pour afficher un cadre, et le dernier pour afficher une image.

Image non disponible

II. Mise en oeuvre :▲

Tout sera mis dans le script de ressources. Le contrôle static est principalement destiné à la décoration.

Le contrôle static à quelques nom prédéfini comme élément de boîte de dialogue dans les ressources, tel que LTEXT ou ICON que l'on à déjà vu ou bien CTEXT dans cet exemple. Ceux n'étant pas défini devront être construit avec le générique CONTROL. Ce sera le cas du cadre en relief (style SS_ETCHEDFRAME) ou de l'image (style SS_BITMAP). N'hésitez pas à consulter l'aide API Win32 pour voir les différents styles.

Sélectionnez

#include <windows.h>

img BITMAP image.bmp

DIALOG1 DIALOG

60, 60, 160, 120

STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "Static"

BEGIN

CONTROL "", -1, "STATIC", SS_ETCHEDFRAME ,10, 2, 140, 116

CTEXT "Simple texte", -1, 20, 5, 120, 20

CONTROL "img", -1, "STATIC", SS_BITMAP ,20, 20, 140, 12

END

Quand il a le style SS_BITMAP le champ texte du contrôle static doit être le nom de la ressource (img dans l'exemple). Ressource que l'on a mis dans les ressources bien sûr :

Sélectionnez

img BITMAP image.bmp

Le fichier image.bmp doit être présent sur le disque avant compilation. Il ne sera pas nécessaire avec l'exécutable puisqu'il est intégré aux ressources.

Le code de l'application ce passe de commentaire. On y appelle seulement la boîte de dialogue.

III. Code complet :▲

III-A. resource.rc :▲

Sélectionnez

#include <windows.h>

img BITMAP image.bmp

DIALOG1 DIALOG

60, 60, 160, 120

STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "Static"

BEGIN

CONTROL "", -1, "STATIC", SS_ETCHEDFRAME ,10, 2, 140, 116

CTEXT "Simple texte", -1, 20, 5, 120, 20

CONTROL "img", -1, "STATIC", SS_BITMAP ,20, 20, 140, 12

END

III-B. winmain.c :▲

Sélectionnez

#include <windows.h>

BOOL APIENTRY DlgProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

DialogBox(hInstance,"DIALOG1",NULL,(DLGPROC)DlgProc);

return 0;

}

/*****************************************************************************/

BOOL APIENTRY DlgProc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)

{

switch (uMsg)

{

case WM_COMMAND:

if (LOWORD(wParam) == IDCANCEL)

{

EndDialog(hDlg,0);

return TRUE;

}

default:

return FALSE;

}

}

J'ai testé les compilations avec C++ Builder et Dev-C++.

A vos PC.


27 - Les contrôles supplémentaires de Windows 12Progress Bar 27 .htm

I. Introduction

Windows permet d'utiliser des contrôles supplémentaires gérés par une bibliothèque dynamique appelée : "Common control library" (comctrl32.dll)
Nous allons commencer par le plus simple : la barre de progression. Vous connaissez la barre de progression, celle qui nous fait patienter le temps d'un traitement long.
Nous allons pour cet exemple créer une boîte de dialogue appelée directement depuis la fonction d'entrée WinMain. Elle comportera : une barre de progression et un bouton qui la fera avancer d'une unité à chaque appui.

Image non disponible

II. Mise en oeuvre

Les compilateurs de ressources ne connaissant pas ce contrôle nous utiliserons le type générique CONTROL pour le créer.

Sélectionnez

CONTROL "", IDE_PB1, PROGRESS_CLASS, 0 ,10, 20, 140, 12

Il est suivi du texte, puis de son identificateur, puis du nom de sa classe de fenêtre, puis de son style, puis de sa position et de ses dimensions.
Ces contrôles pour être utilisés doivent s'assurer que comctrl32.dll est chargée. C'est le rôle de la fonction InitCommonControls :

Sélectionnez

InitCommonControls();

Cette fonction doit être appelée une seule fois avant l'utilisation du contrôle.

III. La barre de progression

L'utilisation de la barre de progression est très simple. Avant son utilisation, vous lui envoyez un message PBM_SETRANGE pour définir ses bornes :

Sélectionnez

SendDlgItemMessage(hDlg, IDE_PB1, PBM_SETRANGE, 0, MAKELONG(0,10));

Celle-ci dans les mots de poids faible et de poids fort du paramètre lParam, reconstitué avec la macro MAKELONG.
Ensuite, vous initialisez un pas de progression en lui envoyant un message PBM_SETSTEP :

Sélectionnez

SendDlgItemMessage(hDlg, IDE_PB1, PBM_SETSTEP, 1, 0);

Le pas étant passé avec le paramètre wParam du message.
Enfin, vous la faites avancer d'un pas avec le message PBM_STEPIT :

Sélectionnez

SendDlgItemMessage(hDlg, IDE_PB1, PBM_STEPIT, 0, 0);

Ce que nous ferons à chaque action du bouton.

IV. Code complet

resource.h :

Sélectionnez

#define IDE_PB1 101

resource.rc :

Sélectionnez

#include <windows.h>
#include "resource.h"
DIALOG1 DIALOG
60, 60, 160, 80
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "ProgressBar"
BEGIN
DEFPUSHBUTTON "Progress", IDOK, 56, 55, 42, 12
CONTROL "", IDE_PB1, PROGRESS_CLASS, 0 ,10, 20, 140, 12
END

winmain.c :

Sélectionnez

#include <windows.h>
#include <commctrl.h>
#include "resource.h"
BOOL APIENTRY DlgProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
InitCommonControls();
DialogBox(hInstance,"DIALOG1",NULL,(DLGPROC)DlgProc);
return 0;
}
/******************************************************************************/
BOOL APIENTRY DlgProc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
SendDlgItemMessage(hDlg, IDE_PB1, PBM_SETRANGE, 0, MAKELONG(0,10));
SendDlgItemMessage(hDlg, IDE_PB1, PBM_SETSTEP, 1, 0);
return TRUE;
case WM_COMMAND :
if (LOWORD(wParam) == IDOK)
{
SendDlgItemMessage(hDlg, IDE_PB1, PBM_STEPIT, 0, 0);
return TRUE;
}
if (LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg,0);
return TRUE;
}
default :
return FALSE;
}
}

Dans le document suivant : "Le contrôle barre d'état" . Un contrôle de la "Common control library" sera créé sur la fenêtre principale.

J'ai testé les compilations avec C++ Builder et Dev-C++.
Le compilateur de ressources de Dev-C++ n'ayant pas accepté la constante PROGRESS_CLASS, j'ai dû la remplacer par son véritable nom de classe de fenêtre.

Sélectionnez

CONTROL "", IDE_PB1, "msctls_progress32", 0 ,10, 20, 140, 12

N'oubliez pas d'ajouter la bibliothèque d'importation "libcomctl32.a" avec Dev-C++ (Load object files dans Project options).

À voir un autre exemple d'utilisation d'un contrôle de comctrl32.dll : La barre d'état .

À vos PC.


28 - La barre d'état ou Status Bar_.htm

Après la barre de progression nous allons voir un deuxième contrôle de la bibliothèque dynamique "Common control library" : la barre d'état. Vous l'avez sûrement vu dans divers logiciel, c'est celle qui ce trouve en bas des fenêtres et qui sert à donner des informations.

Elle sera créée dans la fenêtre pricipale. Nous reprendrons l'exemple du chapitre 10 "Communiquer avec les boîtes de dialogue" , auquel nous ajouterons une barre d'état. Elle affichera des informations textuelles pour chaque option de menu. Ceci quand elle seront sélectionnées.

Image non disponible

II. Mise en oeuvre :▲

Comme elle sera sur la fenêtre principale nous la créons avec la fonction CreateStatusWindow. On aurait pu utiliser CreateWindow mais CreateStatusWindow est plus simple.

Sélectionnez

InitCommonControls();

hsb = CreateStatusWindow(WS_CHILD | WS_VISIBLE, "Texte", hwnd, -1);

Comme pour la barre de progression , il faut appeler la fonction InitCommonControls avant de créer la barre. C'est tout pour sa création. Mais il va falloir gérer son redimensionnement quand les dimensions de sa fenêtre parent sont changées par l'utilisateur. Cela se fera comme pour le contrôle d'édition, à la réception du message WM_SIZE de la fenêtre parent.

Sélectionnez

case WM_SIZE:

{

RECT sbRect;

UINT sbheight;

GetWindowRect(hsb, &sbRect);

sbheight = sbRect.bottom - sbRect.top;

MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam)-sbheight,

TRUE);

MoveWindow(hsb, 0, HIWORD(lParam)-sbheight, LOWORD(lParam),

sbheight, TRUE);

return 0;

}

On récupére ses dimensions à l'aide de la fonction GetWindowRect, puis on positionne le contrôle d'édition avec la fonction MoveWindow afin qu'il laisse la place pour la barre d'état et enfin on positionne la barre d'état sur la place restante toujours avec la fonction MoveWindow.

Notre but est maintenant d'y afficher des informations quand des options de menus sont sélectionnées (ça ne veut pas dire actionnées, mais seulement quelles sont en surbrillances). Pour cela nous allons intercepter le message WM_MENUSELECT qui est envoyé à la fenêtre quand une option de son menu est sélectionnée.

Sélectionnez

case WM_MENUSELECT:

{

if (lParam == (LONG)GetMenu(hwnd))

{

if(LOWORD(wParam) == 0)

SendMessage(hsb, SB_SETTEXT, 0, (LONG)"Menu fichiers");

if(LOWORD(wParam) == 1)

SendMessage(hsb, SB_SETTEXT, 0, (LONG)"Menu Aide");

}

else

{

if(LOWORD(wParam) == IDM_QUIT)

SendMessage(hsb, SB_SETTEXT, 0,

(LONG)"Quitter l'application.");

if(LOWORD(wParam) == IDM_NEW)

SendMessage(hsb, SB_SETTEXT, 0,

(LONG)"Crée un nouveau fichier.");

if(LOWORD(wParam) == IDM_PARAM)

SendMessage(hsb, SB_SETTEXT, 0,

(LONG)"Changer les paramètres.");

if(LOWORD(wParam) == IDM_ABOUT)

SendMessage(hsb, SB_SETTEXT, 0, (LONG)"A Propos...");

}

return 0;

}

Puis nous testons le mot de poids faible du paramètre wParam qui est joint au message pour savoir de quelle option il s'agit. Ce mot contient identificateur de l'option. Le paramètre lParam contient le handle de menu, nous nous en servons pour savoir s'il s'agit du menu principal ou non.

Suite à la réception du message, nous modifions le texte de la barre d'état en lui envoyant un message SB_SETTEXT avec un pointeur sur la chaîne de caractère contenant le texte souhaité dans son paramètre lParam.

Nous changerons de même ce texte quand le menu ne sera plus en cours d'utilisation :

Sélectionnez

case WM_EXITMENULOOP:

SendMessage(hsb, SB_SETTEXT, 0, (LONG)"Texte");

return 0;

III. Code complet :▲

III-A. resource.h : ▲

Sélectionnez

#define IDM_QUIT 1

#define IDM_NEW 2

#define IDM_ABOUT 3

#define IDM_PARAM 4

#define IDE_EDIT1 101

#define IDSB_SB1 201

III-B. resource.rc : ▲

Sélectionnez

#include <windows.h>

#include "resource.h"

1 ICON icone.ico

2 ICON autre.ico

LEMENU MENU

BEGIN

POPUP "Fichier"

BEGIN

MENUITEM "&Nouveau\tCtrl+N", IDM_NEW

MENUITEM "&Paramètres...\tCtrl+P", IDM_PARAM

MENUITEM SEPARATOR

MENUITEM "Quitter\tAlt+F4", IDM_QUIT

END

POPUP "Aide"

BEGIN

MENUITEM "A propos...", IDM_ABOUT

END

END

LesAccel ACCELERATORS

BEGIN

"N", IDM_NEW, CONTROL, VIRTKEY

"P", IDM_PARAM, CONTROL, VIRTKEY

VK_F1, IDM_ABOUT, VIRTKEY

END

DIALOG2 DIALOG

60, 60, 182, 70

STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "Paramètres"

BEGIN

DEFPUSHBUTTON "OK", IDOK, 36, 42, 42, 12

PUSHBUTTON "Cancel", IDCANCEL, 96, 42, 42, 12

EDITTEXT IDE_EDIT1, 88, 15, 74, 12

LTEXT "Titre de la fenêtre", -1, 24, 18, 60, 10

END

DIALOG1 DIALOG

60, 60, 160, 80

STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "A propos"

BEGIN

DEFPUSHBUTTON "Ok", IDOK, 56, 50, 42, 12

ICON 2, -1, 20, 15, 32, 32

LTEXT "Mon beau programme !", -1, 60, 18, 80, 10

END

III-C. winmain.c : ▲

Sélectionnez

#include <windows.h>

#include <commctrl.h>

#include "resource.h"

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);

BOOL APIENTRY Dialog2Proc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

HWND hwnd;

MSG msg;

WNDCLASS wc;

HACCEL haccel;

hinst = hinstance;

wc.style = 0 ;

wc.lpfnWndProc = MainWndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hinstance;

wc.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(2));

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = NULL;

wc.lpszMenuName = "LEMENU";

wc.lpszClassName = "MaWinClass";

if(!RegisterClass(&wc)) return FALSE;

hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,

NULL, NULL, hinstance, NULL);

if (!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow);

haccel = LoadAccelerators(hinstance, "LesAccel");

while (GetMessage(&msg, NULL, 0, 0))

{

if (!TranslateAccelerator(hwnd, haccel, &msg))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

}

return msg.wParam;

}

/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

static HWND hEdit;

static HWND hsb;

static BOOL EditNotChg = TRUE;

switch (uMsg)

{

case WM_CREATE:

{

HFONT hFont;

hEdit = CreateWindowEx(WS_EX_CLIENTEDGE ,"edit", "Texte",

WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,

0, 0, 0, 0, hwnd, NULL, hinst, NULL);

hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);

SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);

SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,

MAKELONG(5, 5));

InitCommonControls();

hsb = CreateStatusWindow(WS_CHILD | WS_VISIBLE, "Texte", hwnd, -1);

return 0;

}

case WM_CLOSE:

if(EditNotChg ||

MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \

vouloir fermer l'application ?"

,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)

DestroyWindow(hwnd);

return 0;

case WM_COMMAND:

if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);

if(LOWORD(wParam) == IDM_NEW)

if(EditNotChg ||

MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \

vouloir fermer votre travail ?"

,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)

{

SendMessage(hEdit,WM_SETTEXT,0,(LPARAM)"");

EditNotChg = TRUE;

}

if(LOWORD(wParam) == IDM_ABOUT)

DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);

if(LOWORD(wParam) == IDM_PARAM)

DialogBoxParam(hinst, "DIALOG2" , hwnd,

(DLGPROC)Dialog2Proc, (LPARAM)hwnd);

if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;

return 0;

case WM_SIZE:

{

RECT sbRect;

UINT sbheight;

GetWindowRect(hsb, &sbRect);

sbheight = sbRect.bottom - sbRect.top;

MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam)-sbheight,

TRUE);

MoveWindow(hsb, 0, HIWORD(lParam)-sbheight, LOWORD(lParam),

sbheight, TRUE);

return 0;

}

case WM_MENUSELECT:

{

if (lParam == (LONG)GetMenu(hwnd))

{

if(LOWORD(wParam) == 0)

SendMessage(hsb, SB_SETTEXT, 0, (LONG)"Menu fichiers");

if(LOWORD(wParam) == 1)

SendMessage(hsb, SB_SETTEXT, 0, (LONG)"Menu Aide");

}

else

{

if(LOWORD(wParam) == IDM_QUIT)

SendMessage(hsb, SB_SETTEXT, 0,

(LONG)"Quitter l'application.");

if(LOWORD(wParam) == IDM_NEW)

SendMessage(hsb, SB_SETTEXT, 0,

(LONG)"Crée un nouveau fichier.");

if(LOWORD(wParam) == IDM_PARAM)

SendMessage(hsb, SB_SETTEXT, 0,

(LONG)"Changer les paramètres.");

if(LOWORD(wParam) == IDM_ABOUT)

SendMessage(hsb, SB_SETTEXT, 0, (LONG)"A Propos...");

}

return 0;

}

case WM_EXITMENULOOP:

SendMessage(hsb, SB_SETTEXT, 0, (LONG)"Texte");

return 0;

case WM_DESTROY:

PostQuitMessage(0);

return 0;

default:

return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

}

/*****************************************************************************/

BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)

{

switch (uMsg)

{

case WM_INITDIALOG:

return TRUE;

case WM_COMMAND:

if (LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK)

{

EndDialog(hDlg,0);

return TRUE;

}

default:

return FALSE;

}

}

/*****************************************************************************/

BOOL APIENTRY Dialog2Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)

{

static HWND hParent;

switch (uMsg)

{

case WM_INITDIALOG:

{

int WindowTextLength;

CHAR *buffer;

hParent = (HWND)lParam;

WindowTextLength = GetWindowTextLength(hParent);

buffer = (CHAR*)LocalAlloc(LMEM_FIXED, WindowTextLength+1);

GetWindowText(hParent, buffer, WindowTextLength+1);

SetDlgItemText(hDlg, IDE_EDIT1, buffer);

LocalFree(buffer);

}

return TRUE;

case WM_COMMAND:

if (LOWORD(wParam) == IDOK )

{

CHAR st[128];

GetDlgItemText(hDlg, IDE_EDIT1, st, 128);

SetWindowText(hParent,st);

EndDialog(hDlg,0);

return TRUE;

}

if (LOWORD(wParam) == IDCANCEL )

{

EndDialog(hDlg,0);

return TRUE;

}

default:

return FALSE;

}

}

J'ai testé les compilations avec C++ Builder et Dev-C++.

N'oubliez pas d'ajouter la librairie d'importation "libcomctl32.a" avec Dev-C++ (Load object files dans Project options).

A voir aussi : la barre de progression .

A vos PC.


29 - Le contrôle Trackbar ou Slider_.htm

I. Introduction :▲

Nous allons voir le contrôle TrackBar appellé aussi slider. C'est le curseur à glissière de comportement semblable à l'ascenseur ou scroll bar. Vous l'utilisez pour modifier le volume du son sur votre PC. Il fait aussi partie des contrôles de la "Common control library" (comctrl32.dll)

Afin de l'implémenter, nous reprendrons l'exemple de la scroll bar ou ascenseur , où nous remplacerons l'ascenseur horizontal par une trackbar horizontale afin de modifier la largeur du trait de la figure.

Image non disponible

II. Mise en oeuvre :▲

La track bar sera ajoutée depuis les ressources et identifié par la constante ID_TB1.

Sélectionnez

CONTROL "", ID_TB1, TRACKBAR_CLASS, TBS_AUTOTICKS | WS_TABSTOP

,96, 24, 60, 16

Comme pour l'exemple de l'ascenseur la largeur du trait sera mémorisé dans la variable globale Trait.

Son utilisation est plus simple que celle de l'ascenseur. Ne pas oublier l'initialisation de la "Common control library".

Initialisation des données :

Sélectionnez

static HWND htb;

switch (uMsg)

{

case WM_INITDIALOG:

{

htb = GetDlgItem(hDlg, ID_TB1);

SendMessage(htb, TBM_SETRANGE,FALSE, MAKELONG(1, 10));

SendMessage(htb, TBM_SETPOS,TRUE, Trait);

SetDlgItemInt(hDlg, ID_LT1, Trait, FALSE);

return TRUE;

}

A l'initialisation de la boîte de dialogue, nous récupérons son handle de fenêtre avec la fonction GetDlgItem. Ensuite, nous lui envoyons un message TBM_SETRANGE pour initialiser ses bornes. Enfin un message TBM_SETPOS pour initialiser sa position.

La gestion de sa position visuelle est automatique contrairement à l'ascenseur. Donc au traitement du message WM_HSCROLL de la boîte de dialogue, nous nous contenterons de mettre à jour le texte affichant la largeur désirée du trait.

Sélectionnez

case WM_HSCROLL:

SetDlgItemInt(hDlg, ID_LT1, SendMessage(htb, TBM_GETPOS, 0, 0), FALSE);

return TRUE;

Pour cela on récupère la position de la TrackBar en lui envoyant un message TBM_GETPOS et on envoie le résultat au champ texte avec la fonction SetDlgItemInt.

A la fermeture de la boîte de dialogue par Ok :

Sélectionnez

case WM_COMMAND:

if (LOWORD(wParam) == IDOK)

{

Trait = SendMessage(htb, TBM_GETPOS, 0, 0);

EndDialog(hDlg,DB_OK);

return TRUE;

}

Nous récupérons sa position en lui envoyant un message TBM_GETPOS et nous l'affectons à la variable Trait.

III. Code complet :▲

III-A. resource.h :▲

Sélectionnez

#define IDM_QUIT 1

#define IDM_PROP 2

#define ID_TB1 101

#define ID_CB1 102

#define ID_LT1 103

III-B. resource.rc :▲

Sélectionnez

#include <windows.h>

#include "resource.h"

LEMENU MENU

BEGIN

POPUP "Fichier"

BEGIN

MENUITEM "Propriétés...", IDM_PROP

MENUITEM SEPARATOR

MENUITEM "Quitter", IDM_QUIT

END

END

DIALOG1 DIALOG

110, 0, 175, 90

STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "Propriétés de la figure"

BEGIN

LTEXT "Figure", -1, 35, 10, 30, 10

COMBOBOX ID_CB1, 20, 24, 56, 45, CBS_DROPDOWNLIST | WS_TABSTOP

LTEXT "Trait", -1, 115, 10, 30, 10

CONTROL "", ID_TB1, TRACKBAR_CLASS, TBS_AUTOTICKS | WS_TABSTOP

,96, 24, 60, 16

LTEXT "Larg :", -1, 110, 44, 25, 10

RTEXT "0", ID_LT1, 130, 44, 10, 10

DEFPUSHBUTTON "OK", IDOK, 36, 65, 42, 12

PUSHBUTTON "Cancel", IDCANCEL, 96, 65, 42, 12

END

III-C. winmain.c :▲

Sélectionnez

#include <windows.h>

#include <commctrl.h>

#include "resource.h"

#define DB_OK 1

#define ID_CARRE 0

#define ID_CERCLE 1

#define ID_TRIANGLE 2

UINT Forme;

UINT Trait;

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

HWND hwnd;

MSG msg;

WNDCLASS wc;

hinst = hinstance;

wc.style = 0;

wc.lpfnWndProc = MainWndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hinstance;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);

wc.lpszMenuName = "LEMENU";

wc.lpszClassName = "MaWinClass";

if(!RegisterClass(&wc)) return FALSE;

hwnd = CreateWindow("MaWinClass", "Check Box", WS_OVERLAPPED | WS_SYSMENU,

CW_USEDEFAULT, CW_USEDEFAULT, 400, 260,

NULL, NULL, hinstance, NULL);

if (!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow);

UpdateWindow(hwnd);

InitCommonControls();

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

/****************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

switch (uMsg)

{

case WM_CREATE:

Forme = ID_CARRE;

Trait = 1;

return 0;

case WM_COMMAND:

if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);

if(LOWORD(wParam) == IDM_PROP)

{

if(DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc)

== DB_OK)

InvalidateRect(hwnd,NULL,TRUE);

}

return 0;

case WM_PAINT :

{

PAINTSTRUCT ps;

HDC hdc ;

HPEN hpen, hpOld;

POINT ptTriangle[3];

ptTriangle[0].x = 20;

ptTriangle[0].y = 20;

ptTriangle[1].x = 20;

ptTriangle[1].y = 170;

ptTriangle[2].x = 170;

ptTriangle[2].y = 95;

hdc = BeginPaint(hwnd, &ps);

hpen = CreatePen(PS_SOLID, Trait, 0);

hpOld = (HPEN)SelectObject(hdc,hpen);

if(Forme == ID_CARRE) Rectangle(hdc, 20, 20, 170, 170);

if(Forme == ID_CERCLE) Ellipse(hdc, 20, 20, 170, 170);

if(Forme == ID_TRIANGLE) Polygon(hdc, ptTriangle, 3);

SelectObject(hdc,hpOld);

DeleteObject(hpen);

EndPaint(hwnd, &ps);

return 0;

}

case WM_DESTROY:

PostQuitMessage(0);

return 0;

default:

return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

}

/******************************************************************************/

BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)

{

static HWND htb;

switch (uMsg)

{

case WM_INITDIALOG:

{

SendDlgItemMessage(hDlg, ID_CB1, CB_ADDSTRING, 0, (LONG)"Carré");

SendDlgItemMessage(hDlg, ID_CB1, CB_ADDSTRING, 0, (LONG)"Cercle");

SendDlgItemMessage(hDlg, ID_CB1, CB_ADDSTRING, 0, (LONG)"Triangle");

SendDlgItemMessage(hDlg, ID_CB1, CB_SETCURSEL, Forme, 0);

htb = GetDlgItem(hDlg, ID_TB1);

SendMessage(htb, TBM_SETRANGE,FALSE, MAKELONG(1, 10));

SendMessage(htb, TBM_SETPOS,TRUE, Trait);

SetDlgItemInt(hDlg, ID_LT1, Trait, FALSE);

return TRUE;

}

case WM_HSCROLL:

SetDlgItemInt(hDlg, ID_LT1, SendMessage(htb, TBM_GETPOS, 0, 0), FALSE);

return TRUE;

case WM_COMMAND:

if (LOWORD(wParam) == IDOK)

{

Trait = SendMessage(htb, TBM_GETPOS, 0, 0);

Forme = SendDlgItemMessage(hDlg, ID_CB1, CB_GETCURSEL, 0, 0);

EndDialog(hDlg,DB_OK);

return TRUE;

}

if (LOWORD(wParam) == IDCANCEL)

{

EndDialog(hDlg,0);

return TRUE;

}

default:

return FALSE;

}

}

J'ai testé les compilations avec C++ Builder et DevC++.
Comme pour la Progress Bar le compilateur de ressources de Dev-C++ n'ayant pas accepté la constante TRACKBAR_CLASS, j'ai du la remplacer par son véritable nom de classe de fenêtre :

Sélectionnez

CONTROL "", ID_TB1, "msctls_trackbar32", TBS_AUTOTICKS | WS_TABSTOP

,96, 24, 60, 16

A vos PC.


30 - Le contrôle List View.htm

I. Introduction▲

Nous allons aborder un autre contrôle de la "Common control library" : le contrôle List View. Vous le connaissez, c'est lui qui affiche la liste des fichiers dans l'explorateur de fichiers de Windows.
Nous allons voir une mise en uvre simple avec l'affichage de ses éléments uniquement sous forme de grandes icônes.
Pour cet exemple, nous reprendrons l'exemple du chapitre 14 "Sous-classement de fenêtres" où l'on placera la list view à gauche du contrôle d'édition.

Image non disponible

II. Listes d'images▲

La List View nous impose d'aborder un autre élément de Windows : la liste d'images ou Image List. C'est un conteneur d'images que l'on devra passé comme paramètre à la List View pour qu'elle puisse nous afficher une image associée à chaque élément de la liste. La liste d'images doit bien sûr être remplie par nos soins. Pour notre exemple ce sera des icônes. Pour ne pas faire la chasse aux icônes, nous les chargerons depuis le fichier shell32.dll qui en contient une grande quantité dans ses ressources. Nous ferons tout cela à la création de la fenêtre principale.

Sélectionnez

case WM_CREATE:
{
HINSTANCE dllhinst;
HICON hIcon;
HIMAGELIST himgList;
InitCommonControls();
himgList = ImageList_Create(GetSystemMetrics(SM_CXICON),
GetSystemMetrics(SM_CYICON), ILC_COLOR32 , 3, 3);
ImageList_SetBkColor(himgList, GetSysColor(COLOR_WINDOW));
dllhinst = LoadLibrary("shell32.dll");
hIcon = LoadIcon(dllhinst, MAKEINTRESOURCE(4));
ImageList_AddIcon(himgList, hIcon);
hIcon = LoadIcon(dllhinst, MAKEINTRESOURCE(6));
ImageList_AddIcon(himgList, hIcon);
hIcon = LoadIcon(dllhinst, MAKEINTRESOURCE(12));
ImageList_AddIcon(himgList, hIcon);
FreeLibrary(dllhinst);

On crée la liste d'images avec la fonction ImageList_Create, on charge la dll contenant les icônes puis on charge les trois icônes pour les ajouter à la liste d'images à l'aide de la fonction ImageList_AddIcon. On donnera à la couleur du fond des icônes la couleur de fond par défaut des fenêtres à l'aide de la fonction ImageList_SetBkColor.

III. Mise en uvre▲

Maintenant que nous avons tous les éléments, nous allons construire la ListView.

Sélectionnez

LV_ITEM lvi;
ZeroMemory(&lvi, sizeof(LV_ITEM));
lvi.mask = LVIF_TEXT | LVIF_IMAGE ;
hListView =CreateWindowEx(WS_EX_CLIENTEDGE , WC_LISTVIEW, "",
WS_CHILD | WS_VISIBLE , 0, 0, 0, 0, hwnd, NULL, hinst, NULL);
ListView_SetImageList(hListView, himgList, LVSIL_NORMAL);
lvi.pszText = "Option 3";
lvi.iImage = 0;
ListView_InsertItem(hListView, &lvi);
lvi.pszText = "Option 2";
lvi.iImage = 1;
ListView_InsertItem(hListView, &lvi);
lvi.pszText = "Option 1";
lvi.iImage = 2;
ListView_InsertItem(hListView, &lvi);

La List View est créée classiquement avec la fonction CreateWindowEx. Pour insérer des éléments dans la liste, on doit remplir une structure LV_ITEM contenant les caractéristiques de l'élément à insérer, puis l'insérer avec la macroListView_InsertItem.

Pour terminer l'exemple, on affichera un texte différent dans le contrôle d'édition selon l'élément qui sera sélectionné dans la liste. Nous utiliserons pour cela une notification qui est envoyée à sa fenêtre parent lorsque sa sélection a changé. Le message de notification est WM_NOTIFY. Il est joint à ce message dans son paramètre lParam, un pointeur sur une structure NM_LISTVIEW qui contient elle-même une structure NMHDR qui, elle contient le code de la notification. Si ce code est LVN_ITEMCHANGED ce qui veut dire que la sélection a changé nous changerons le texte du contrôle d'édition. L'indice du nouvel élément sélectionné est dans le champ iItem de la structure NM_LISTVIEW.

Sélectionnez

case WM_NOTIFY:
{
LPNM_LISTVIEW pnlv = (LPNM_LISTVIEW)lParam;
if(pnlv->hdr.code == LVN_ITEMCHANGED)
{
if(pnlv->iItem == 0)
SetWindowText(hEdit,
"\r\nBien !\r\n\r\nVous avez sélectionné l'option 1.");
if(pnlv->iItem == 1)
SetWindowText(hEdit,
"\r\nBravo !\r\n\r\nVous avez sélectionné l'option 2.");
if(pnlv->iItem == 2)
SetWindowText(hEdit,
"\r\nParfait !\r\n\r\nVous avez sélectionné l'option 3.");
}
return 0;
}

Ce passage n'est qu'un petit aperçu de la ListView. Je vous propose de consulter l'aide API win32 pour plus d'informations.

IV. Code complet▲

Sélectionnez

#include <windows.h>
#include <commctrl.h>
#ifdef STRICT
WNDPROC wpOrigEditProc ;
#else
FARPROC wpOrigEditProc ;
#endif
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT APIENTRY EditProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE hinst;
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
hinst = hinstance;
wc.style = 0 ;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "List View", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hEdit;
static HWND hListView;
switch (uMsg)
{
case WM_CREATE:
{
LV_ITEM lvi;
HINSTANCE dllhinst;
HICON hIcon;
HIMAGELIST himgList;
InitCommonControls();
himgList = ImageList_Create(GetSystemMetrics(SM_CXICON),
GetSystemMetrics(SM_CYICON), ILC_COLOR32 , 3, 3);
ImageList_SetBkColor(himgList, GetSysColor(COLOR_WINDOW));
dllhinst = LoadLibrary("shell32.dll");
hIcon = LoadIcon(dllhinst, MAKEINTRESOURCE(4));
ImageList_AddIcon(himgList, hIcon);
hIcon = LoadIcon(dllhinst, MAKEINTRESOURCE(6));
ImageList_AddIcon(himgList, hIcon);
hIcon = LoadIcon(dllhinst, MAKEINTRESOURCE(12));
ImageList_AddIcon(himgList, hIcon);
FreeLibrary(dllhinst);
ZeroMemory(&lvi, sizeof(LV_ITEM));
lvi.mask = LVIF_TEXT | LVIF_IMAGE ;
hListView =CreateWindowEx(WS_EX_CLIENTEDGE , WC_LISTVIEW, "",
WS_CHILD | WS_VISIBLE , 0, 0, 0, 0, hwnd, NULL, hinst, NULL);
hEdit =CreateWindowEx(WS_EX_CLIENTEDGE , "edit",
"\r\nTest de la ListView.",
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 0, hwnd, NULL, hinst, NULL);
SetClassLong(hEdit, GCL_HCURSOR, (LONG)LoadCursor(NULL, IDC_ARROW));
wpOrigEditProc = (PVOID)SetWindowLong(hEdit,
GWL_WNDPROC, (LONG)EditProc);
ListView_SetImageList(hListView, himgList, LVSIL_NORMAL);
lvi.pszText = "Option 3";
lvi.iImage = 0;
ListView_InsertItem(hListView, &lvi);
lvi.pszText = "Option 2";
lvi.iImage = 1;
ListView_InsertItem(hListView, &lvi);
lvi.pszText = "Option 1";
lvi.iImage = 2;
ListView_InsertItem(hListView, &lvi);
return 0;
}
case WM_NOTIFY:
{
LPNM_LISTVIEW pnlv = (LPNM_LISTVIEW)lParam;
if(pnlv->hdr.code == LVN_ITEMCHANGED)
{
if(pnlv->iItem == 0)
SetWindowText(hEdit,
"\r\nBien !\r\n\r\nVous avez sélectionné l'option 1.");
if(pnlv->iItem == 1)
SetWindowText(hEdit,
"\r\nBravo !\r\n\r\nVous avez sélectionné l'option 2.");
if(pnlv->iItem == 2)
SetWindowText(hEdit,
"\r\nParfait !\r\n\r\nVous avez sélectionné l'option 3.");
}
return 0;
}
case WM_SIZE :
MoveWindow(hEdit, 100, 0, LOWORD(lParam)-100, HIWORD(lParam), TRUE);
MoveWindow(hListView, 0, 0, 100, HIWORD(lParam), TRUE);
return 0;
case WM_DESTROY :
SetWindowLong(hEdit, GWL_WNDPROC,(LONG) wpOrigEditProc);
PostQuitMessage(0);
return 0;
default :
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
/******************************************************************************/
LRESULT APIENTRY EditProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_SETFOCUS || uMsg == WM_CONTEXTMENU || uMsg == WM_CHAR ||
uMsg == WM_KEYDOWN)
return 0;
return CallWindowProc(wpOrigEditProc, hwnd, uMsg, wParam, lParam);
}

J'ai testé les compilations avec C++ Builder et DevC++.

À vos PC.


31 - Le contrôle Tree View_.htm

I. Introduction :▲

Nous allons aborder un autre contrôle de la "Common control library" : Le contrôle Tree View. Vous le connaissez, c'est lui qui affiche l'arborescence des dossiers dans le panneau de gauche de l'explorateur de fichiers de Windows.

Nous allons voir une mise en oeuvre simple.

Pour cet exemple, nous reprendrons l'exemple de la List View que l'on remplacera par le contrôle Tree View.

Image non disponible

II. Mise en oeuvre :▲

Nous devrons comme pour la List View construire une liste d'images. Le contrôle Tree View se construit traditionnellement avec la fonction CreateWindowEx, avec des flag de style propre à lui même influençant sur son aspect visuel.

Sélectionnez

case WM_CREATE:

{

TV_INSERTSTRUCT tviis;

TV_ITEM tvi;

HTREEITEM hitem;

InitCommonControls();

hTreeView =CreateWindowEx(WS_EX_CLIENTEDGE , WC_TREEVIEW, "",

WS_CHILD | WS_VISIBLE | TVS_HASLINES | TVS_LINESATROOT

| TVS_HASBUTTONS | TVS_SHOWSELALWAYS

, 0, 0, 0, 0, hwnd, NULL, hinst, NULL);

TreeView_SetImageList(hTreeView, himgList, TVSIL_NORMAL);

tviis.hInsertAfter = TVI_LAST;

ZeroMemory(&(tviis.item), sizeof(TV_ITEM));

tviis.item.mask = TVIF_TEXT | TVIF_IMAGE |

TVIF_SELECTEDIMAGE | TVIF_PARAM ;

tviis.hParent = TVI_ROOT;

tviis.item.iImage = 0;

tviis.item.iSelectedImage = 1;

tviis.item.lParam = 1;

tviis.item.pszText = "Option 1";

hitem = TreeView_InsertItem(hTreeView, &tviis);

tviis.hParent = hitem ;

tviis.item.iImage = 2;

tviis.item.iSelectedImage = 2;

tviis.item.lParam = 3;

tviis.item.pszText = "Sous Option A";

TreeView_InsertItem(hTreeView, &tviis);

tviis.item.iImage = 3;

tviis.item.iSelectedImage = 3;

tviis.item.lParam = 4;

tviis.item.pszText = "Sous Option B";

TreeView_InsertItem(hTreeView, &tviis);

tviis.hParent = TVI_ROOT;

tviis.item.iImage = 0;

tviis.item.iSelectedImage = 1;

tviis.item.lParam = 2;

tviis.item.pszText = "Option 2";

hitem = TreeView_InsertItem(hTreeView, &tviis);

tviis.hParent = hitem ;

tviis.item.iImage = 3;

tviis.item.iSelectedImage = 3;

tviis.item.lParam = 5;

tviis.item.pszText = "Sous Option C";

TreeView_InsertItem(hTreeView, &tviis);

tviis.item.iSelectedImage = 2;

tviis.item.iImage = 2;

tviis.item.lParam = 6;

tviis.item.pszText = "Sous Option D";

TreeView_InsertItem(hTreeView, &tviis);

La liste d'images lui est affectée avec la macro TreeView_SetImageList. Pour l'insertion des éléments nous utiliserons une structure spécifique TV_INSERTSTRUCT décrivant les modalités d'insertion et qui contiendra entre autre une structure TV_ITEM décrivant l'élément à insérer. Son champ Mask influence l'affichage et les propriétés le l'élément. Cette structure une fois rempli, est passée comme paramètre à la macro d'insertion TreeView_InsertItem.

Sous C++ Builder 6 la compilation n'a pas passée. La structure TV_INSERTSTRUCT possède une union de deux structure TV_ITEM et TV_ITEMEX J'ai donc du modifié le code pour nommer l'union.

Je n'ai pas eu ce problème sur BCB 4 ou Dev-C++.

Sélectionnez

tviis.hInsertAfter = TVI_LAST;

ZeroMemory(&(tviis.u.item), sizeof(TV_ITEM));

tviis.u.item.mask = TVIF_TEXT | TVIF_IMAGE |

TVIF_SELECTEDIMAGE | TVIF_PARAM ;

tviis.hParent = TVI_ROOT;

tviis.u.item.iImage = 0;

tviis.u.item.iSelectedImage = 1;

tviis.u.item.lParam = 1;

tviis.u.item.pszText = "Option 1";

hitem = TreeView_InsertItem(hTreeView, &tviis);

/* ...

*/

Nous allons maintenant effectuer une action quand un de ses éléments est sélectionné. Comme pour la List View c'est sur la réception du message WM_NOTIFY. Pour différencier chaque élément nous utiliserons le champ lParam de la structure TV_ITEM. Il permet d'enregistrer une valeur sur 32 bits pour chaque élément.

Sélectionnez

case WM_NOTIFY:

{

HTREEITEM hitem;

LPNM_TREEVIEW pntv = (LPNM_TREEVIEW)lParam;

if(pntv->hdr.code == TVN_SELCHANGED)

{

if(pntv->itemNew.lParam == 1)

SetWindowText(hEdit,

"\r\n\r\n\r\nVous avez sélectionné l'option 1.");

if(pntv->itemNew.lParam == 2)

SetWindowText(hEdit,

"\r\n\r\n\r\nVous avez sélectionné l'option 2.");

if(pntv->itemNew.lParam == 3)

SetWindowText(hEdit,

"\r\n\r\n\r\nVous avez sélectionné la sous Option A.");

if(pntv->itemNew.lParam == 4)

SetWindowText(hEdit,

"\r\n\r\n\r\nVous avez sélectionné la sous Option B.");

if(pntv->itemNew.lParam == 5)

SetWindowText(hEdit,

"\r\n\r\n\r\nVous avez sélectionné la sous Option C.");

if(pntv->itemNew.lParam == 6)

SetWindowText(hEdit,

"\r\n\r\n\r\nVous avez sélectionné la sous Option D.");

}

return 0;

}

Ce passage n'est qu'un petit aperçu du contrôle Tree View. Je vous propose de consulter l'aide API win32 pour plus d'information.

III. Code complet :▲

Sélectionnez

#include <windows.h>

#include <commctrl.h>

#ifdef STRICT

WNDPROC wpOrigEditProc ;

#else

FARPROC wpOrigEditProc ;

#endif

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

LRESULT APIENTRY EditProc(HWND, UINT, WPARAM, LPARAM);

HINSTANCE hinst;

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

HWND hwnd;

MSG msg;

WNDCLASS wc;

hinst = hinstance;

wc.style = 0 ;

wc.lpfnWndProc = MainWndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hinstance;

wc.hIcon = NULL;

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = NULL;

wc.lpszMenuName = NULL;

wc.lpszClassName = "MaWinClass";

if(!RegisterClass(&wc)) return FALSE;

hwnd = CreateWindow("MaWinClass", "Tree View", WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, 600, 300,

NULL, NULL, hinstance, NULL);

if (!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow);

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

static HWND hEdit;

static HWND hTreeView;

switch (uMsg)

{

case WM_CREATE:

{

TV_INSERTSTRUCT tviis;

TV_ITEM tvi;

HTREEITEM hitem;

HINSTANCE dllhinst;

HICON hIcon;

HIMAGELIST himgList;

InitCommonControls();

himgList = ImageList_Create(GetSystemMetrics(SM_CXSMICON),

GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 , 4, 4);

ImageList_SetBkColor(himgList, GetSysColor(COLOR_WINDOW));

dllhinst = LoadLibrary("shell32.dll");

hIcon = LoadIcon(dllhinst, MAKEINTRESOURCE(4));

ImageList_AddIcon(himgList, hIcon);

hIcon = LoadIcon(dllhinst, MAKEINTRESOURCE(5));

ImageList_AddIcon(himgList, hIcon);

hIcon = LoadIcon(dllhinst, MAKEINTRESOURCE(6));

ImageList_AddIcon(himgList, hIcon);

hIcon = LoadIcon(dllhinst, MAKEINTRESOURCE(12));

ImageList_AddIcon(himgList, hIcon);

FreeLibrary(dllhinst);

hTreeView =CreateWindowEx(WS_EX_CLIENTEDGE , WC_TREEVIEW, "",

WS_CHILD | WS_VISIBLE | TVS_HASLINES | TVS_LINESATROOT

| TVS_HASBUTTONS | TVS_SHOWSELALWAYS

, 0, 0, 0, 0, hwnd, NULL, hinst, NULL);

hEdit =CreateWindowEx(WS_EX_CLIENTEDGE , "edit",

"\r\nTest de la ListView.",

WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,

0, 0, 0, 0, hwnd, NULL, hinst, NULL);

SetClassLong(hEdit, GCL_HCURSOR, (LONG)LoadCursor(NULL, IDC_ARROW));

wpOrigEditProc = (PVOID)SetWindowLong(hEdit,

GWL_WNDPROC, (LONG)EditProc);

TreeView_SetImageList(hTreeView, himgList, TVSIL_NORMAL);

tviis.hInsertAfter = TVI_LAST;

ZeroMemory(&(tviis.item), sizeof(TV_ITEM));

tviis.item.mask = TVIF_TEXT | TVIF_IMAGE |

TVIF_SELECTEDIMAGE | TVIF_PARAM ;

tviis.hParent = TVI_ROOT;

tviis.item.iImage = 0;

tviis.item.iSelectedImage = 1;

tviis.item.lParam = 1;

tviis.item.pszText = "Option 1";

hitem = TreeView_InsertItem(hTreeView, &tviis);

tviis.hParent = hitem ;

tviis.item.iImage = 2;

tviis.item.iSelectedImage = 2;

tviis.item.lParam = 3;

tviis.item.pszText = "Sous Option A";

TreeView_InsertItem(hTreeView, &tviis);

tviis.item.iImage = 3;

tviis.item.iSelectedImage = 3;

tviis.item.lParam = 4;

tviis.item.pszText = "Sous Option B";

TreeView_InsertItem(hTreeView, &tviis);

tviis.hParent = TVI_ROOT;

tviis.item.iImage = 0;

tviis.item.iSelectedImage = 1;

tviis.item.lParam = 2;

tviis.item.pszText = "Option 2";

hitem = TreeView_InsertItem(hTreeView, &tviis);

tviis.hParent = hitem ;

tviis.item.iImage = 3;

tviis.item.iSelectedImage = 3;

tviis.item.lParam = 5;

tviis.item.pszText = "Sous Option C";

TreeView_InsertItem(hTreeView, &tviis);

tviis.item.iSelectedImage = 2;

tviis.item.iImage = 2;

tviis.item.lParam = 6;

tviis.item.pszText = "Sous Option D";

TreeView_InsertItem(hTreeView, &tviis);

return 0;

}

case WM_NOTIFY:

{

HTREEITEM hitem;

LPNM_TREEVIEW pntv = (LPNM_TREEVIEW)lParam;

if(pntv->hdr.code == TVN_SELCHANGED)

{

if(pntv->itemNew.lParam == 1)

SetWindowText(hEdit,

"\r\n\r\n\r\nVous avez sélectionné l'option 1.");

if(pntv->itemNew.lParam == 2)

SetWindowText(hEdit,

"\r\n\r\n\r\nVous avez sélectionné l'option 2.");

if(pntv->itemNew.lParam == 3)

SetWindowText(hEdit,

"\r\n\r\n\r\nVous avez sélectionné la sous Option A.");

if(pntv->itemNew.lParam == 4)

SetWindowText(hEdit,

"\r\n\r\n\r\nVous avez sélectionné la sous Option B.");

if(pntv->itemNew.lParam == 5)

SetWindowText(hEdit,

"\r\n\r\n\r\nVous avez sélectionné la sous Option C.");

if(pntv->itemNew.lParam == 6)

SetWindowText(hEdit,

"\r\n\r\n\r\nVous avez sélectionné la sous Option D.");

}

return 0;

}

case WM_SIZE:

MoveWindow(hEdit, 200, 0, LOWORD(lParam)-200, HIWORD(lParam), TRUE);

MoveWindow(hTreeView, 0, 0, 200, HIWORD(lParam), TRUE);

return 0;

case WM_DESTROY:

SetWindowLong(hEdit, GWL_WNDPROC,(LONG) wpOrigEditProc);

PostQuitMessage(0);

return 0;

default:

return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

}

/******************************************************************************/

LRESULT APIENTRY EditProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

if (uMsg == WM_SETFOCUS || uMsg == WM_CONTEXTMENU || uMsg == WM_CHAR ||

uMsg == WM_KEYDOWN)

return 0;

return CallWindowProc(wpOrigEditProc, hwnd, uMsg, wParam, lParam);

}

J'ai testé les compilations avec C++ Builder et DevC++.

A vos PC.


32 - Le contrôle Tab ou boîte à onglets_.htm

I. Introduction :▲

Nous allons aborder un autre contrôle de la "Common control library" : Le contrôle Tab ou boîte à onglets.

Pour cet exemple on construira une boîte à onglets avec 2 onglets sur une fenêtre. Selon l'onglet sélectionné on affichera soit un contrôle d'édition soit une case à cocher.

Image non disponible

II. Mise en oeuvre :▲

Le contrôle Tab se construit traditionnellement avec la fonction CreateWindow ou CreateWindowEx. L'insertion de ses éléments ce fait à l'aide de la macro TabCtrl_InsertItem. Elle reçoit comme paramètre une structure TC_ITEM décrivant l'élément à insérer.

Sélectionnez

case WM_CREATE:

{

TC_ITEM tie;

InitCommonControls();

hTabs = CreateWindowEx(0 , WC_TABCONTROL, "",

WS_CHILD | WS_VISIBLE,

10, 10, 300, 200, hwnd, NULL, hinst, NULL);

hEdit =CreateWindowEx(WS_EX_CLIENTEDGE , "edit",

"\r\nTest de la boîte à onglet.",

WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,

15, 40, 290, 163, hwnd, NULL, hinst, NULL);

hstatic =CreateWindowEx(0 , "static", "Etes vous content ?",

WS_CHILD,

60, 80, 160, 30, hwnd, NULL, hinst, NULL);

hCB =CreateWindowEx(0 , "button", "Oui, je suis content.",

WS_CHILD | BS_AUTOCHECKBOX,

60, 100, 160, 30, hwnd, NULL, hinst, NULL);

tie.mask = TCIF_TEXT;

tie.pszText = "Volet 1";

TabCtrl_InsertItem(hTabs, 1, &tie);

tie.pszText = "Volet 2";

TabCtrl_InsertItem(hTabs, 2, &tie);

Dans l'exemple il recevra uniquement un texte. On aurait pu lui mettre une image en utilisant une liste d'images comme pour les contrôles List View ou Tree View.

Le contrôle ne gère pas automatiquement son contenu. Nous allons donc le faire sur réception de la notification TCN_SELCHANGE. Et selon l'onglet sélectionné nous afficherons les contrôles de notre choix à l'aide de la fonction ShowWindow. On récupère l'onglet sélectionné avec la macro TabCtrl_GetCurSel.

Sélectionnez

case WM_NOTIFY:

{

LPNMHDR pnmhdr = (LPNMHDR)lParam ;

if(pnmhdr->code == TCN_SELCHANGE && TabCtrl_GetCurSel(hTabs) == 0)

{

ShowWindow(hCB,SW_HIDE);

ShowWindow(hstatic,SW_HIDE);

ShowWindow(hEdit,SW_SHOW);

}

if(pnmhdr->code == TCN_SELCHANGE && TabCtrl_GetCurSel(hTabs) == 1)

{

ShowWindow(hEdit,SW_HIDE);

ShowWindow(hstatic,SW_SHOW);

ShowWindow(hCB,SW_SHOW);

}

return 0;

Ce passage n'est qu'un petit aperçu du contrôle Tab. Je vous propose de consulter l'aide API win32 pour plus d'information.

III. Code complet :▲

Sélectionnez

#include <windows.h>

#include <commctrl.h>

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

HWND hwnd;

MSG msg;

WNDCLASS wc;

hinst = hinstance;

wc.style = 0;

wc.lpfnWndProc = MainWndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hinstance;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);

wc.lpszMenuName = NULL;

wc.lpszClassName = "MaWinClass";

if(!RegisterClass(&wc)) return FALSE;

hwnd = CreateWindow("MaWinClass", "Titre",

WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX,

CW_USEDEFAULT, CW_USEDEFAULT, 330, 253,

NULL, NULL, hinstance, NULL);

if (!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow);

UpdateWindow(hwnd);

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

static HWND hTabs;

static HWND hEdit;

static HWND hCB;

static HWND hstatic;

switch (uMsg)

{

case WM_CREATE:

{

TC_ITEM tie;

InitCommonControls();

hTabs = CreateWindowEx(0 , WC_TABCONTROL, "",

WS_CHILD | WS_VISIBLE,

10, 10, 300, 200, hwnd, NULL, hinst, NULL);

hEdit =CreateWindowEx(WS_EX_CLIENTEDGE , "edit",

"\r\nTest de la boîte à onglet.",

WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,

15, 40, 290, 163, hwnd, NULL, hinst, NULL);

hstatic =CreateWindowEx(0 , "static", "Etes vous content ?",

WS_CHILD,

60, 80, 160, 30, hwnd, NULL, hinst, NULL);

hCB =CreateWindowEx(0 , "button", "Oui, je suis content.",

WS_CHILD | BS_AUTOCHECKBOX,

60, 100, 160, 30, hwnd, NULL, hinst, NULL);

tie.mask = TCIF_TEXT;

tie.pszText = "Volet 1";

TabCtrl_InsertItem(hTabs, 1, &tie);

tie.pszText = "Volet 2";

TabCtrl_InsertItem(hTabs, 2, &tie);

return 0;

}

case WM_NOTIFY:

{

LPNMHDR pnmhdr = (LPNMHDR)lParam ;

if(pnmhdr->code == TCN_SELCHANGE && TabCtrl_GetCurSel(hTabs) == 0)

{

ShowWindow(hCB,SW_HIDE);

ShowWindow(hstatic,SW_HIDE);

ShowWindow(hEdit,SW_SHOW);

}

if(pnmhdr->code == TCN_SELCHANGE && TabCtrl_GetCurSel(hTabs) == 1)

{

ShowWindow(hEdit,SW_HIDE);

ShowWindow(hstatic,SW_SHOW);

ShowWindow(hCB,SW_SHOW);

}

return 0;

}

case WM_DESTROY:

PostQuitMessage(0);

return 0;

default:

return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

}

J'ai testé les compilations avec C++ Builder et DevC++.

A vos PC.


33 - Le contrôle Toolbar_.htm

I. Introduction :▲

Nous allons aborder un autre contrôle de la "Common control library" : Le contrôle Toolbar.

Pour cet exemple on reprendra l'exemple "Communiquer avec les boîtes de dialogue". On lui ajoutera une Toolbar avec 3 boutons. Dans cet exemple les boutons auront des grandes images et un texte, la Toolbar aura le style flat.

Image non disponible

I-A. Mise en oeuvre :▲

Le contrôle Toolbar se construit traditionnellement avec la fonction CreateWindow ou CreateWindowEx.

Sélectionnez

htb = CreateWindowEx(0, TOOLBARCLASSNAME, NULL,

WS_CHILD | WS_VISIBLE | TBSTYLE_FLAT,

0, 0, 0, 0, hwnd, NULL, hinst, NULL);

La Toolbar possède sa propre liste d'images. Nous devons juste la remplir. Pour cela on utilise une structure TBADDBITMAP avec son champ hInst initialisé avec le handle d'instance du fichier contenant les images et son champ nID avec l'identificateur de la ressource image. Dans notre exemple nous nous faciliterons la tâche en prenant des images standard de Windows (champ hInst à HINST_COMMCTRL et champ nID à IDB_STD_LARGE_COLOR pour de grande images). Ensuite on envoie le message TB_ADDBITMAP à la Toolbar avec l'adresse de la structure comme paramètre.

Sélectionnez

TBADDBITMAP tbab;

tbab.hInst = HINST_COMMCTRL ;

tbab.nID = IDB_STD_LARGE_COLOR;

SendMessage(htb, TB_ADDBITMAP, 1, (WPARAM)&tbab);

Ensuite il faut ajouter les boutons à la Toolbar. Pour cela nous devons tout d'abord remplir un tableau de structure TBBUTTON. Chaque élément du tableau correspond à un bouton. Le champ iBitmap de la structure reçoit l'indice de l'image, Le champ idCommand reçoit l'identificateur de la commande. Ce peut être la même que celle d'une option de menu. Ce sera d'ailleurs le cas dans l'exemple. Le champ fsState reçoit l'état du bouton. Le champ fsStyle reçoit le style du bouton. Le troisième bouton dans l'exemple à le style TBSTYLE_SEP car c'est un séparateur. Le champ iString reçoit l'indice de la chaîne de caractères qui correspond au bouton. Elle doit être préalablement chargée dans la liste de chaîne de la Toolbar en lui envoyant un message TB_ADDSTRING.

Sélectionnez

TBBUTTON tbb[4];

tbb[0].iBitmap = STD_FILENEW;

tbb[0].idCommand = IDM_NEW;

tbb[0].fsState = TBSTATE_ENABLED;

tbb[0].fsStyle = TBSTYLE_BUTTON;

tbb[0].dwData = 0;

tbb[0].iString = SendMessage(htb, TB_ADDSTRING, 0,

(LPARAM)"Nouveau");

tbb[1].iBitmap = STD_PROPERTIES;

tbb[1].idCommand = IDM_PARAM;

tbb[1].fsState = TBSTATE_ENABLED;

tbb[1].fsStyle = TBSTYLE_BUTTON;

tbb[1].dwData = 0;

tbb[1].iString = SendMessage(htb, TB_ADDSTRING, 0,

(LPARAM)"Paramètre");

tbb[2].iBitmap = 0;

tbb[2].idCommand = -1;

tbb[2].fsState = 0;

tbb[2].fsStyle = TBSTYLE_SEP;

tbb[2].dwData = 0;

tbb[2].iString = -1;

tbb[3].iBitmap = STD_HELP;

tbb[3].idCommand = IDM_ABOUT;

tbb[3].fsState = TBSTATE_ENABLED;

tbb[3].fsStyle = TBSTYLE_BUTTON;

tbb[3].dwData = 0;

tbb[3].iString = SendMessage(htb, TB_ADDSTRING, 0,

(LPARAM)"A propos");

SendMessage(htb, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);

SendMessage(htb, TB_ADDBUTTONS, 4, (LPARAM)&tbb);

SendMessage(htb, TB_AUTOSIZE, 0, 0);

Quand le tableau est rempli, il faut ajouter les boutons à la Toolbar en lui envoyant un message TB_ADDBUTTONS avec l'adresse du tableau comme paramètre. Avant d'envoyer ce message il est important d'envoyer la taille de la structure TBBUTTON à la Toolbar en lui envoyant un message TB_BUTTONSTRUCTSIZE. Pour adapter la taille de la Toolbar à celle des boutons on lui envoie un message TB_AUTOSIZE. Pour voir toutes les possibilités de la Toolbar, je vous propose de consulter l'aide API Win32.

II. Code complet :▲

II-A. resource.h :▲

Sélectionnez

#define IDM_QUIT 1

define IDM_NEW 2

#define IDM_ABOUT 3

#define IDM_PARAM 4

#define IDE_EDIT1 101

II-B. resource.rc :▲

Sélectionnez

#include <windows.h>

#include "resource.h"

1 ICON icone.ico

2 ICON autre.ico

LEMENU MENU

BEGIN

POPUP "Fichier"

BEGIN

MENUITEM "&Nouveau", IDM_NEW

MENUITEM "&Paramètres...", IDM_PARAM

MENUITEM SEPARATOR

MENUITEM "Quitter\tAlt+F4", IDM_QUIT

END

POPUP "Aide"

BEGIN

MENUITEM "A propos...", IDM_ABOUT

END

END

DIALOG2 DIALOG

60, 60, 182, 70

STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "Paramètres"

BEGIN

DEFPUSHBUTTON "OK", IDOK, 36, 42, 42, 12

PUSHBUTTON "Cancel", IDCANCEL, 96, 42, 42, 12

EDITTEXT IDE_EDIT1, 88, 15, 74, 12

LTEXT "Titre de la fenêtre", -1, 24, 18, 60, 10

END

DIALOG1 DIALOG

60, 60, 160, 80

STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "A propos"

BEGIN

DEFPUSHBUTTON "Ok", IDOK, 56, 50, 42, 12

ICON 2, -1, 20, 15, 32, 32

LTEXT "Mon beau programme !", -1, 60, 18, 80, 10

END

II-C. winmain.c :▲

Sélectionnez

#include <windows.h>

#include <commctrl.h>

#include "resource.h"

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);

BOOL APIENTRY Dialog2Proc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

HWND hwnd;

MSG msg;

WNDCLASS wc;

hinst = hinstance;

wc.style = 0 ;

wc.lpfnWndProc = MainWndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hinstance;

wc.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(2));

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = NULL;

wc.lpszMenuName = "LEMENU";

wc.lpszClassName = "MaWinClass";

if(!RegisterClass(&wc)) return FALSE;

hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,

NULL, NULL, hinstance, NULL);

if (!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow);

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

/****************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

static HWND hEdit;

static HWND htb;

static BOOL EditNotChg = TRUE;

switch (uMsg)

{

case WM_CREATE:

{

TBADDBITMAP tbab;

TBBUTTON tbb[4];

HFONT hFont;

hEdit = CreateWindowEx(WS_EX_CLIENTEDGE ,"edit", "Texte",

WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL,

0, 0, 0, 0, hwnd, NULL, hinst, NULL);

hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);

SendMessage(hEdit,WM_SETFONT,(UINT)hFont,TRUE);

SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,

MAKELONG(5, 5));

InitCommonControls();

htb = CreateWindowEx(0, TOOLBARCLASSNAME, NULL,

WS_CHILD | WS_VISIBLE | TBSTYLE_FLAT,

0, 0, 0, 0, hwnd, NULL, hinst, NULL);

tbab.hInst = HINST_COMMCTRL ;

tbab.nID = IDB_STD_LARGE_COLOR;

SendMessage(htb, TB_ADDBITMAP, 1, (WPARAM)&tbab);

tbb[0].iBitmap = STD_FILENEW;

tbb[0].idCommand = IDM_NEW;

tbb[0].fsState = TBSTATE_ENABLED;

tbb[0].fsStyle = TBSTYLE_BUTTON;

tbb[0].dwData = 0;

tbb[0].iString = SendMessage(htb, TB_ADDSTRING, 0,

(LPARAM)"Nouveau");

tbb[1].iBitmap = STD_PROPERTIES;

tbb[1].idCommand = IDM_PARAM;

tbb[1].fsState = TBSTATE_ENABLED;

tbb[1].fsStyle = TBSTYLE_BUTTON;

tbb[1].dwData = 0;

tbb[1].iString = SendMessage(htb, TB_ADDSTRING, 0,

(LPARAM)"Paramètre");

tbb[2].iBitmap = 0;

tbb[2].idCommand = -1;

tbb[2].fsState = 0;

tbb[2].fsStyle = TBSTYLE_SEP;

tbb[2].dwData = 0;

tbb[2].iString = -1;

tbb[3].iBitmap = STD_HELP;

tbb[3].idCommand = IDM_ABOUT;

tbb[3].fsState = TBSTATE_ENABLED;

tbb[3].fsStyle = TBSTYLE_BUTTON;

tbb[3].dwData = 0;

tbb[3].iString = SendMessage(htb, TB_ADDSTRING, 0,

(LPARAM)"A propos");

SendMessage(htb, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);

SendMessage(htb, TB_ADDBUTTONS, 4, (LPARAM)&tbb);

SendMessage(htb, TB_AUTOSIZE, 0, 0);

return 0;

}

case WM_CLOSE:

if(EditNotChg ||

MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \

vouloir fermer l'application ?"

,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)

DestroyWindow(hwnd);

return 0;

case WM_COMMAND:

if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);

if(LOWORD(wParam) == IDM_NEW)

if(EditNotChg ||

MessageBox(hwnd,"Le texte a été modifié.\r\nEtes vous sûr de \

vouloir fermer votre travail ?"

,"Question ?",MB_YESNO | MB_ICONQUESTION ) == IDYES)

{

SendMessage(hEdit,WM_SETTEXT,0,(LPARAM)"");

EditNotChg = TRUE;

}

if(LOWORD(wParam) == IDM_ABOUT)

DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);

if(LOWORD(wParam) == IDM_PARAM)

DialogBoxParam(hinst, "DIALOG2" , hwnd,

(DLGPROC)Dialog2Proc, (LPARAM)hwnd);

if(HIWORD(wParam) == EN_CHANGE) EditNotChg = FALSE;

return 0;

case WM_SIZE:

{

RECT sbRect;

UINT tbheight;

GetWindowRect(htb, &sbRect);

tbheight = sbRect.bottom - sbRect.top;

MoveWindow(htb, 0, 0, LOWORD(lParam), tbheight, TRUE);

MoveWindow(hEdit, 0, tbheight, LOWORD(lParam),

HIWORD(lParam)-tbheight, TRUE);

return 0;

}

case WM_DESTROY:

PostQuitMessage(0);

return 0;

default:

return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

}

/*****************************************************************************/

BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)

{

switch (uMsg)

{

case WM_INITDIALOG:

return TRUE;

case WM_COMMAND:

if (LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK)

{

EndDialog(hDlg,0);

return TRUE;

}

default:

return FALSE;

}

}

/******************************************************************************/

BOOL APIENTRY Dialog2Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)

{

static HWND hParent;

switch (uMsg)

{

case WM_INITDIALOG:

{

int WindowTextLength;

CHAR *buffer;

hParent = (HWND)lParam;

WindowTextLength = GetWindowTextLength(hParent);

buffer = (CHAR*)LocalAlloc(LMEM_FIXED, WindowTextLength+1);

GetWindowText(hParent, buffer, WindowTextLength+1);

SetDlgItemText(hDlg, IDE_EDIT1, buffer);

LocalFree(buffer);

}

return TRUE;

case WM_COMMAND:

if (LOWORD(wParam) == IDOK )

{

CHAR st[128];

GetDlgItemText(hDlg, IDE_EDIT1, st, 128);

SetWindowText(hParent,st);

EndDialog(hDlg,0);

return TRUE;

}

if (LOWORD(wParam) == IDCANCEL )

{

EndDialog(hDlg,0);

return TRUE;

}

default:

return FALSE;

}

}

J'ai testé les compilations avec C++ Builder et DevC++.
La constante de style flat (TBSTYLE_FLAT) de la Toolbar n'est pas défini dans le fichier commctrl.h avec la version de Dev-C++ en ma possession. J'ai donc du la remplacer par sa valeur (0x0800) :

Sélectionnez

htb = CreateWindowEx(0, TOOLBARCLASSNAME, NULL,

WS_CHILD | WS_VISIBLE | 0x0800,

0, 0, 0, 0, hwnd, NULL, hinst, NULL);

III. Utilisation d'images personnelles :▲

Dans l'exemple précédent nous avons utilisée des images du système. Nous allons brièvement voir comment utiliser des images personnelles. Il faut remplir une structure de type TBADDBITMAP : le champ hInst avec le handle d'instance du module contenant les images et le champ nID avec l'identificateur de la ressource image. Ensuite il suffit d'ajouter les images en envoyant le message TB_ADDBITMAP à la toolbar. Le champ iBitmap des structures TBBUTTON devra être rempli avec l'indice de l'image.

Sélectionnez

TBADDBITMAP tbab;

/* ... */

tbab.hInst = hinst;

tbab.nID = IDB_IMG;

SendMessage(htb, TB_ADDBITMAP, 4, (WPARAM)&tbab);

tbb[0].iBitmap = 5;

/* ... */

Plus récement a été apporté une autre solution qui a l'avantage de pouvoir utiliser des images 32 bits. C'est d'utiliser une Listes d'images que l'on ajoutera à la toolbar.

Sélectionnez

HIMAGELIST himgList;

/* ... */

himgList = ImageList_Create(24, 24, ILC_MASK |ILC_COLOR32 , 0, 6);

ImageList_AddMasked(himgList,

LoadBitmap(hinst,MAKEINTRESOURCE(IDB_IMG)), 0x00FF00FF);

SendMessage(htb, TB_SETIMAGELIST , 0, (LPARAM)himgList);

tbb[0].iBitmap = 5;

/* ... */

Le message TB_SETIMAGELIST n'est pas défini dans le fichier commctrl.h avec la version de Dev-C++ en ma possession. J'ai donc du définir ce message pour l'utiliser.

Sélectionnez

#define TB_SETIMAGELIST (WM_USER + 48)

A vos PC.


34 - Le contrôle ToolTips_.htm

I. Introduction :▲

Dans ce chapitre, nous allons aborder le contrôle ToolTips.

On appelle contrôle ToolTips les bulles d'aide qui apparaissent quand on positionne le curseur de la souris au dessus d'un contrôle.

Image non disponible

II. Création et initialisation du contrôle :▲

Le contrôle ToolTips ce crée avec CreateWindow comme tout autres contrôles. Il peut gérer plusieurs tips (bulle d'aide). En fait c'est le même contrôle qui est caché, déplassé, rendu visible et ou son texte est modifié selon le contexte. Pour chaque bulle d'aide on doit remplir une structure de type TOOLINFO qui doit nottament être rempli avec le handle du contrôle à qui est affecté la bulle d'aide mais aussi avec ses coordonnées. Elle sera enregistré dans le contrôle Tooltips en lui envoyant cette structure par adresse à l'aide d'un message TTM_ADDTOOL.

Sélectionnez

TOOLINFO ti;

RECT rect;

InitCommonControls();

hTTip = CreateWindow(TOOLTIPS_CLASS, NULL,

WS_POPUP | TTS_NOPREFIX | TTS_BALLOON,

CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

hwnd, NULL, hInst, NULL);

GetClientRect (hwnd, &rect);

ti.cbSize = sizeof(TOOLINFO);

ti.uFlags = TTF_SUBCLASS;

ti.hwnd = hwnd;

ti.hinst = hInst;

ti.uId = 0;

ti.lpszText = "C'est la fenêtre principale.";

ti.rect.left = rect.left;

ti.rect.top = rect.top;

ti.rect.right = rect.right;

ti.rect.bottom = rect.bottom;

SendMessage(hTTip, TTM_ADDTOOL, 0, (LPARAM) &ti);

GetClientRect (hwnd, &rect);

ti.hwnd = hBtn;

ti.uId = 0;

ti.lpszText = "C'est un bouton, vous pouvez appuyer dessus.";

ti.rect.left = rect.left;

ti.rect.top = rect.top;

ti.rect.right = rect.right;

ti.rect.bottom = rect.bottom;

SendMessage(hTTip, TTM_ADDTOOL, 0, (LPARAM) &ti);

III. Code complet :▲

III-A. main.c :▲

Sélectionnez

#include <windows.h>

#include <commctrl.h>

LONG APIENTRY WndProc(HWND hwnd, UINT uMsg, UINT wParam, LONG lParam);

HINSTANCE hInst;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

HWND hwndMain ;

MSG msg;

WNDCLASS wc;

hInst = hInstance;

wc.style = 0;

wc.lpfnWndProc = (WNDPROC) WndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hInstance;

wc.hIcon = LoadIcon((HINSTANCE) NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);

wc.lpszMenuName = MAKEINTRESOURCE(1); ;

wc.lpszClassName = "MainWndClass";

if (!RegisterClass(&wc)) return FALSE;

hwndMain = CreateWindow("MainWndClass", "WinApi",

WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,

360, 280, NULL, NULL, hInstance, NULL);

ShowWindow(hwndMain, nCmdShow);

UpdateWindow(hwndMain);

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return 0;

}

LONG APIENTRY WndProc(HWND hwnd, UINT uMsg, UINT wParam, LONG lParam)

{

static HWND hBtn;

static HWND hTTip;

switch ( uMsg ) {

case WM_CREATE:

{

TOOLINFO ti;

RECT rect;

hBtn = CreateWindow("button", "Bouton", WS_CHILD | WS_VISIBLE,

100, 100, 96, 24, hwnd, NULL, hInst, NULL);

InitCommonControls();

hTTip = CreateWindow(TOOLTIPS_CLASS, NULL,

WS_POPUP | TTS_NOPREFIX | TTS_BALLOON,

CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

hwnd, NULL, hInst, NULL);

SendMessage(hTTip, TTM_SETTITLE, TTI_INFO , (LPARAM)"Titre");

GetClientRect (hwnd, &rect);

ti.cbSize = sizeof(TOOLINFO);

ti.uFlags = TTF_SUBCLASS;

ti.hwnd = hwnd;

ti.hinst = hInst;

ti.uId = 0;

ti.lpszText = "C'est la fenêtre principale.";

ti.rect.left = rect.left;

ti.rect.top = rect.top;

ti.rect.right = rect.right;

ti.rect.bottom = rect.bottom;

SendMessage(hTTip, TTM_ADDTOOL, 0, (LPARAM) &ti);

GetClientRect (hwnd, &rect);

ti.hwnd = hBtn;

ti.uId = 0;

ti.lpszText = "C'est un bouton, vous pouvez appuyer dessus.";

ti.rect.left = rect.left;

ti.rect.top = rect.top;

ti.rect.right = rect.right;

ti.rect.bottom = rect.bottom;

SendMessage(hTTip, TTM_ADDTOOL, 0, (LPARAM) &ti);

return 0;

}

case WM_DESTROY:

PostQuitMessage(0);

return 0;

}

return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

J'ai testé les compilations avec C++ Builder et DevC++

A vos PC.


35 - Le contrôle Property Sheet_.htm

I. Introduction▲

Nous allons voir un contrôle un peu spécial : Le PropertySheet. Ce n'est pas vraiment un contrôle, mais plutôt une boîte de dialogue préfabriquée. Elle contient déjà des boutons. Elle est utilisée pour faire les wisards mais aussi des boîtes de propriétés avec onglets. C'est sur la base de ce deuxième point que l'on va construire notre exemple.

Image non disponible

II. Création de la boîte de dialogue▲

Avant de construire cette boîte de dialogue, il faut construire le contenu de chaque page de la boîte à onglets. Il est défini dans les ressources comme des boîtes de dialogue indépendantes :

Sélectionnez

TAB1 DIALOG
0, 0, 232, 0
BEGIN
AUTOCHECKBOX "Carré visible ", ID_CB1, 60, 15, 80, 15
AUTOCHECKBOX "Cercle visible", ID_CB2, 60, 30, 80, 15
END
TAB2 DIALOG
0, 0, 232, 0
BEGIN
EDITTEXT IDE_EDIT1, 88, 25, 74, 12
LTEXT "Titre de la fenêtre", -1, 24, 28, 60, 10
END

Ensuite avant de créer la PropertySheet, il faut initialiser les champs d'une structure PROPSHEETHEADER correspondants à la PropertySheet et les champs d'une structure PROPSHEETPAGE, une pour chaque page de la boîte à onglets.

Sélectionnez

PROPSHEETPAGE psp[2];
PROPSHEETHEADER psh;
ZeroMemory(psp, sizeof(psp));
ZeroMemory(&psh, sizeof(psh));
psp[0].dwSize = sizeof(PROPSHEETPAGE);
psp[0].dwFlags = PSP_USETITLE ;
psp[0].hInstance = hinst;
psp[0].u.pszTemplate = "TAB1";
psp[0].pfnDlgProc = Tab1Proc;
psp[0].pszTitle = "Figures";
psp[1].dwSize = sizeof(PROPSHEETPAGE);
psp[1].dwFlags = PSP_USETITLE;
psp[1].hInstance = hinst;
psp[1].u.pszTemplate = "TAB2";
psp[1].pfnDlgProc = Tab2Proc;
psp[1].pszTitle = "Fenêtre";
psh.dwSize = sizeof(PROPSHEETHEADER);
psh.dwFlags = PSH_PROPSHEETPAGE | PSH_NOAPPLYNOW
| PSH_NOCONTEXTHELP;
psh.hwndParent = hwnd;
psh.pszCaption = (LPSTR) "Propriétés";
psh.nPages = sizeof(psp) / sizeof(PROPSHEETPAGE);
psh.u3.ppsp = (LPCPROPSHEETPAGE) &psp;

PropertySheet(&psh);

Certains de ces champs sont assez explicites. Un mot sur le champ pfnDlgProc de PROPSHEETPAGE, il contient un pointeur sur la procédure de fenêtre de la boîte de dialogue (page dans notre cas). Le champ pszTemplate contient lui le nom de la ressource de type DIALOG.
Ensuite il suffit d'appeler la fonction PropertySheet pour ouvrir la boîte de dialogue. Vous pouvez consulter la MSDN pour plus d'information sur ces structures.
(Ces structures contenant des unions u, u2... Sous BCB6, j'ai dû les nommer pour compiler l'exemple avec d'autres compilateurs, il n'est pas nécessaire de les nommer.)

III. Traitement des messages▲

On traite chaque page de la boîte à onglets dans la procédure de fenêtre qui lui a été affectée.

Sélectionnez

BOOL APIENTRY Tab1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
if(prop.CarreVisible == TRUE)
CheckDlgButton(hDlg, ID_CB1, BST_CHECKED);
if(prop.CercleVisible == TRUE)
CheckDlgButton(hDlg, ID_CB2, BST_CHECKED);
return TRUE;
case WM_NOTIFY:
if ((((NMHDR *)lParam)->code)== PSN_APPLY)
{
if(IsDlgButtonChecked(hDlg, ID_CB1) == BST_CHECKED)
prop.CarreVisible = TRUE;
else prop.CarreVisible = FALSE;
if(IsDlgButtonChecked(hDlg, ID_CB2) == BST_CHECKED)
prop.CercleVisible = TRUE;
else prop.CercleVisible = FALSE;
return TRUE;
}
return FALSE;
default :
return FALSE;
}
}
/******************************************************************************/
BOOL APIENTRY Tab2Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
CHAR st[256];
switch (uMsg)
{
case WM_INITDIALOG:
GetWindowText(hwnd, st, 256);
SetDlgItemText(hDlg, IDE_EDIT1, st);
return TRUE;
case WM_NOTIFY:
if ((((NMHDR *)lParam)->code)== PSN_APPLY)
{
GetDlgItemText(hDlg, IDE_EDIT1, st, 256);
SetWindowText(hwnd,st);
return TRUE;
}
return FALSE;
default :
return FALSE;
}
}

Les boutons ne faisant pas partie de nos boîtes de dialogue (pages), la boîte de dialogue parente (PropertySheet) envoie des messages de notification aux pages pour leur signaler qu'un événement a eu lieu (ex. : bouton de la PropertySheet actionné). Dans l'exemple la notification PSN_APPLY signale que la boîte a été validée. On peut donc modifier la fenêtre principale en conséquence.

IV. Code complet▲

resource.h :

Sélectionnez

#define IDM_QUIT 1
#define IDM_PROP 2
#define ID_CB1 101
#define ID_CB2 102
#define IDE_EDIT1 103

resource.rc :

Sélectionnez

#include <windows.h>
#include "resource.h"
LEMENU MENU
BEGIN
POPUP "Fichier"
BEGIN
MENUITEM "Propriétés...", IDM_PROP
MENUITEM SEPARATOR
MENUITEM "Quitter", IDM_QUIT
END
END
TAB1 DIALOG
0, 0, 232, 0
BEGIN
AUTOCHECKBOX "Carré visible ", ID_CB1, 60, 15, 80, 15
AUTOCHECKBOX "Cercle visible", ID_CB2, 60, 30, 80, 15
END
TAB2 DIALOG
0, 0, 232, 0
BEGIN
EDITTEXT IDE_EDIT1, 88, 25, 74, 12
LTEXT "Titre de la fenêtre", -1, 24, 28, 60, 10
END

winmain.c :

Sélectionnez

#include <windows.h>
#include "resource.h"
typedef struct
{
BOOL CarreVisible;
BOOL CercleVisible;
} PROPRIETE ;
PROPRIETE prop;
HINSTANCE hinst;
HWND hwnd;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Tab1Proc(HWND ,UINT ,WPARAM ,LPARAM );
BOOL APIENTRY Tab2Proc(HWND ,UINT ,WPARAM ,LPARAM );
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
WNDCLASS wc;
hinst = hinstance;
wc.style = 0;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wc.lpszMenuName = "LEMENU";
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindowEx(0, "MaWinClass", "Check Box",
WS_OVERLAPPED | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 260,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
prop.CarreVisible = TRUE;
prop.CercleVisible = TRUE;
return 0;
case WM_COMMAND :
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(LOWORD(wParam) == IDM_PROP)
{
PROPSHEETPAGE psp[2];
PROPSHEETHEADER psh;
ZeroMemory(psp, sizeof(psp));
ZeroMemory(&psh, sizeof(psh));
psp[0].dwSize = sizeof(PROPSHEETPAGE);
psp[0].dwFlags = PSP_USETITLE ;
psp[0].hInstance = hinst;
psp[0].u.pszTemplate = "TAB1";
psp[0].pfnDlgProc = Tab1Proc;
psp[0].pszTitle = "Figures";
psp[1].dwSize = sizeof(PROPSHEETPAGE);
psp[1].dwFlags = PSP_USETITLE;
psp[1].hInstance = hinst;
psp[1].u.pszTemplate = "TAB2";
psp[1].pfnDlgProc = Tab2Proc;
psp[1].pszTitle = "Fenêtre";
psh.dwSize = sizeof(PROPSHEETHEADER);
psh.dwFlags = PSH_PROPSHEETPAGE | PSH_NOAPPLYNOW
| PSH_NOCONTEXTHELP;
psh.hwndParent = hwnd;
psh.pszCaption = (LPSTR) "Propriétés";
psh.nPages = sizeof(psp) / sizeof(PROPSHEETPAGE);
psh.u3.ppsp = (LPCPROPSHEETPAGE) &psp;

PropertySheet(&psh);
InvalidateRect(hwnd,NULL,TRUE);
}
return 0;
case WM_PAINT :
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
if(prop.CarreVisible == TRUE) Rectangle(hdc, 20, 20, 170, 170);
if(prop.CercleVisible == TRUE) Ellipse(hdc, 200, 20, 350, 170);
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY :
PostQuitMessage(0);
return 0;
default :
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
/******************************************************************************/
BOOL APIENTRY Tab1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
if(prop.CarreVisible == TRUE)
CheckDlgButton(hDlg, ID_CB1, BST_CHECKED);
if(prop.CercleVisible == TRUE)
CheckDlgButton(hDlg, ID_CB2, BST_CHECKED);
return TRUE;
case WM_NOTIFY:
if ((((NMHDR *)lParam)->code)== PSN_APPLY)
{
if(IsDlgButtonChecked(hDlg, ID_CB1) == BST_CHECKED)
prop.CarreVisible = TRUE;
else prop.CarreVisible = FALSE;
if(IsDlgButtonChecked(hDlg, ID_CB2) == BST_CHECKED)
prop.CercleVisible = TRUE;
else prop.CercleVisible = FALSE;
return TRUE;
}
return FALSE;
default :
return FALSE;
}
}
/******************************************************************************/
BOOL APIENTRY Tab2Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
CHAR st[256];
switch (uMsg)
{
case WM_INITDIALOG:
GetWindowText(hwnd, st, 256);
SetDlgItemText(hDlg, IDE_EDIT1, st);
return TRUE;
case WM_NOTIFY:
if ((((NMHDR *)lParam)->code)== PSN_APPLY)
{
GetDlgItemText(hDlg, IDE_EDIT1, st, 256);
SetWindowText(hwnd,st);
return TRUE;
}
return FALSE;
default :
return FALSE;
}
}

J'ai testé les compilations avec C++ Builder et DevC++.

À vos PC.


36 - Api Windows Les timers_.htm

Les timers.

Vous avez surement entendu parler des Timers, mais vous ne savez pas trop comment les mettre en uvre. Cet article va vous monter qu'il n'y a rien de bien compliqué. Un timer est un système qui envoie un signal régulier à une fréquence déterminée, tel le métronome du musicien. Dans l'exemple nous utiliserons deux timers pour faire clignoter deux rectangles dessiné sur une fenêtre. Pourquoi deux, pour bien montrer la façon de les différencier. Ils auront une fréquence différente. Ils pourront être démarrés ou arrêtés depuis le menu de l'application.

Mise en oeuvre :

Pour créer un timer il suffit d'appeler la fonction SetTimer

SetTimer(hwnd, ID_TIMER_1, 1000, NULL);

Le premier paramètre étant le handle de fenêtre à laquelle il est associé. Le second étant un identificateur (constante numérique) que l'on lui attribut. Le troisième étant le temps en millisecondes de son tempo. Le quatrième que nous n'utiliserons pas, est un pointeur sur une fonction de rappel qui est appelée à chaque signal de tempo. Dans l'exemple nous utiliserons le message qui est envoyé à la fenêtre associé au timer du timer. (Si le timer n'avait pas été associé à une fenêtre, nous aurions été contrains d'utiliser la fonction de rappel). Ce message est WM_TIMER, il se traite comme les autres messages Windows dans la procédure de fenêtre, comme ceux nous avons vu dans les autres chapitres. Ce message dans son paramètre wParam contient l'identificateur du timer qui envoie le message, identificateur que l'on avait passé en deuxième paramètre à SetTimer et qui permet donc de savoir de quel timer il s'agit.

case WM_TIMER:
if(wParam == ID_TIMER_1)
{
/* action */
}
if(wParam == ID_TIMER_2)
{
/* action */
}

Quand vous ne voulez plus du Timer, il suffit de le détruire avec la fonctionKillTimer avec comme paramètre le handle de fenêtre et l'identificateur du timer.

KillTimer(hwnd,ID_TIMER_1);

Pour illustrer l'exemple nos timers feront basculer de true à false et vice versa deux variables booléennes nommées T1 et T2. Ces variables permettront ensuite l'affichage ou non des rectangles dans le traitement du message WM_PAINT. Les timers sont démarrés ou arrêtés depuis les commandes du menu.

Code complet :

resource.h :

#define IDM_START_1 101
#define IDM_STOP_1 102
#define IDM_START_2 103
#define IDM_STOP_2 104
#define IDM_QUIT 105

resource.rc :

#include <windows.h>
#include "resource.h"
LEMENU MENU
BEGIN
POPUP "Fichier"
BEGIN
MENUITEM "Start Timer 1", IDM_START_1
MENUITEM "Stop Timer 1", IDM_STOP_1
MENUITEM SEPARATOR
MENUITEM "Start Timer 2", IDM_START_2
MENUITEM "Stop Timer 2", IDM_STOP_2
MENUITEM SEPARATOR
MENUITEM "Quitter", IDM_QUIT
END
END

winmain.c :

#include <windows.h>
#include "resource.h"
#define ID_TIMER_1 1
#define ID_TIMER_2 2
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wc.lpszMenuName = "LEMENU";
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "Timers", WS_OVERLAPPED | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 260,
NULL, NULL, hInstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static BOOL T1 = TRUE;
static BOOL T2 = TRUE;
switch (uMsg)
{
case WM_CREATE:
return 0;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDM_QUIT :
PostMessage(hwnd, WM_CLOSE,0,0);
break;
case IDM_START_1 :
SetTimer(hwnd,ID_TIMER_1,1000,NULL);
break;
case IDM_START_2 :
SetTimer(hwnd,ID_TIMER_2,500,NULL);
break;
case IDM_STOP_1 :
KillTimer(hwnd,ID_TIMER_1);
T1 = TRUE;
InvalidateRect(hwnd,NULL,TRUE);
break;
case IDM_STOP_2 :
KillTimer(hwnd,ID_TIMER_2);
T2 = TRUE;
InvalidateRect(hwnd,NULL,TRUE);
break;
}
return 0;
case WM_TIMER:
if(wParam == ID_TIMER_1)
{
T1 = !T1;
InvalidateRect(hwnd,NULL,TRUE);
}
if(wParam == ID_TIMER_2)
{
T2 = !T2;
InvalidateRect(hwnd,NULL,TRUE);
}
return 0;
case WM_PAINT :
{
HBRUSH hbNew, hbrOld;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
hbNew = CreateSolidBrush(0x000000FF);
hbrOld = SelectObject(hdc,hbNew);
if(T1) Rectangle(hdc, 30, 20, 180, 170);
SelectObject(hdc,hbrOld);
DeleteObject(hbNew);
hbNew = CreateSolidBrush(0x00007F00);
hbrOld = SelectObject(hdc,hbNew);
if(T2) Rectangle(hdc, 210, 20, 360, 170);
SelectObject(hdc,hbrOld);
DeleteObject(hbNew);
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

A savoir que si le timer n'est pas associé à une fenêtre, c'est la valeur de retour de SetTimer qui sert d'identificateur à passer à KillTimer pour le détruire. Il est possible de changer la fréquence d'un timer déjà démarré par un second appel à SetTimer. N'hésiter pas à consulter le site de msdn pour plus d'informations sur les timers

A vos PC.

CGi


37 - Les menus contextuels.htm

ion▲

Un petit chapitre pour aborder les menus contextuels. Ceux qui apparaissent quand vous faites un clic droit sur une fenêtre. Afin de l'implémenter, nous reprendrons l'exemple de la case à cocher . Nous mettrons le popup menu "Fichier" déjà existant en menu contextuel.

Image non disponible

II. Mise en uvre▲

Le menu étant déjà été préparé dans les ressources :

Sélectionnez

LEMENU MENU
BEGIN
POPUP "Fichier"
BEGIN
MENUITEM "Propriétés...", IDM_PROP
MENUITEM SEPARATOR
MENUITEM "Quitter", IDM_QUIT
END
END

Et ses commandes implémentées. Il nous suffit de l'appeler suite à un clic droit de la souris sur la fenêtre. Mais il existe un message plus approprié WM_CONTEXTMENU qui de plus contient les coordonnées du curseur par rapport à l'écran dans son paramètre lParam.

Sélectionnez

case WM_CONTEXTMENU :
{
HMENU hmenu, hpopup;
hmenu = LoadMenu(hinst,"LEMENU");
hpopup = GetSubMenu(hmenu, 0);
TrackPopupMenuEx(hpopup, 0, LOWORD(lParam), HIWORD(lParam), hwnd, NULL);
DestroyMenu(hmenu);
return 0;
}

Nous chargeons le menu avec LoadMenu. Nous récupérons le handle de son 1er sous-menu avec la fonction GetSubMenu(le 1er a l'indice 0). Nous affichons le menu avec la fonction TrackPopupMenuEx. Le premier paramètre de cette fonction est le handle du popup menu. Le second reçoit des flags influençant son comportement ou affichage. Les trois et quatrième sont sa position à l'écran. Le cinquième et le handle de fenêtre de son parent. Le dernier est une structure contenant une zone rectangulaire de l'écran où le menu ne peut pas s'ouvrir.

III. Code complet▲

resource.h :

Sélectionnez

#define IDM_QUIT 1
#define IDM_PROP 2
#define ID_CB1 101
#define ID_CB2 102

resource.rc :

Sélectionnez

#include <windows.h>
#include "resource.h"
LEMENU MENU
BEGIN
POPUP "Fichier"
BEGIN
MENUITEM "Propriétés...", IDM_PROP
MENUITEM SEPARATOR
MENUITEM "Quitter", IDM_QUIT
END
END
DIALOG1 DIALOG
60, 70, 175, 80
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Propriétés"
BEGIN
DEFPUSHBUTTON "OK", IDOK, 36, 52, 42, 12
PUSHBUTTON "Cancel", IDCANCEL, 96, 52, 42, 12
AUTOCHECKBOX "Carré visible ", ID_CB1, 60, 15, 80, 15
AUTOCHECKBOX "Cercle visible", ID_CB2, 60, 30, 80, 15
END
winmain.c :
#include <windows.h>
#include "resource.h"
typedef struct
{
BOOL CarreVisible;
BOOL CercleVisible;
} PROPRIETE ;
PROPRIETE prop;
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
hinst = hinstance;
wc.style = 0;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wc.lpszMenuName = "LEMENU";
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "Check Box", WS_OVERLAPPED | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 260,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
prop.CarreVisible = TRUE;
prop.CercleVisible = TRUE;
return 0;
case WM_CONTEXTMENU :
{
HMENU hmenu, hpopup;
hmenu = LoadMenu(hinst,"LEMENU");
hpopup = GetSubMenu(hmenu, 0);
TrackPopupMenuEx(hpopup, 0, LOWORD(lParam), HIWORD(lParam), hwnd, NULL);
DestroyMenu(hmenu);
return 0;
}
case WM_COMMAND:
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(LOWORD(wParam) == IDM_PROP)
{
DialogBox(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);
InvalidateRect(hwnd,NULL,TRUE);
}
return 0;
case WM_PAINT :
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
if(prop.CarreVisible == TRUE) Rectangle(hdc, 20, 20, 170, 170);
if(prop.CercleVisible == TRUE) Ellipse(hdc, 200, 20, 350, 170);
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
/******************************************************************************/
BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
{
if(prop.CarreVisible == TRUE)
CheckDlgButton(hDlg, ID_CB1, BST_CHECKED);
if(prop.CercleVisible == TRUE)
CheckDlgButton(hDlg, ID_CB2, BST_CHECKED);
return TRUE;
}
case WM_COMMAND:
if (LOWORD(wParam) == IDOK )
{
if(IsDlgButtonChecked(hDlg, ID_CB1) == BST_CHECKED)
prop.CarreVisible = TRUE;
else prop.CarreVisible = FALSE;
if(IsDlgButtonChecked(hDlg, ID_CB2) == BST_CHECKED)
prop.CercleVisible = TRUE;
else prop.CercleVisible = FALSE;
EndDialog(hDlg,0);
return TRUE;
}
if (LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg,0);
return TRUE;
}
default:
return FALSE;
}
}

J'ai testé les compilations avec C++ Builder et DevC++.
À vos PC.


38 - La boîte de dialogue non modale.htm

I. Introduction▲

Nous avons vu plusieurs exemples d'utilisation des boîtes de dialogue. Elles étaient toutes modales. C'est-à-dire que la fenêtre parente n'est plus accessible tant que la boîte de dialogue n'est pas fermée.
Dans l'exemple suivant, nous allons voir une boîte de dialogue non modale (modeless). C'est-à-dire que la fenêtre parente et toujours accessible, même lorsque la boîte de dialogue est ouverte.
Nous reprendrons l'exemple des boutons radio, mais la boîte de dialogue n'aura plus de bouton OK ou Annuler et l'action sur bouton radio aura un effet immédiat sur la fenêtre principale.

Image non disponible

II. Création de la boîte de dialogue▲

Sélectionnez

CreateDialog(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);

La boîte de dialogue sera créée avec la fonction CreateDialog. Il faut savoir qu'une boîte de dialogue de ce type quand elle est fermée avec la croix, en réalité elle est rendue invisible. Donc si on veut la réafficher une seconde fois, il suffira de la rendre visible.

Sélectionnez

if(hdlg) ShowWindow(hdlg,SW_SHOWNORMAL);
else hdlg = CreateDialog(hinst, "DIALOG1" , hwnd, (DLGPROC)Dialog1Proc);

III. Message de notifications▲

On se servira de la notification BN_CLICKED envoyée à la fenêtre parente des boutons lorsqu'ils sont cliqués. Le mot bas du paramètre wParam du message WM_COMMAND contenant l'identificateur du bouton qui a été cliqué.

Sélectionnez

case WM_COMMAND:
if (HIWORD(wParam) == BN_CLICKED) {
switch (LOWORD(wParam)) {
case ID_RB1:
Trait = FIN;
break;
case ID_RB2:
Trait = MOYEN;
break;
case ID_RB3:
Trait = LARGE;
break;
case ID_CARRE:
Forme = ID_CARRE;
break;
case ID_CERCLE:
Forme = ID_CERCLE;
break;
case ID_TRIANGLE:
Forme = ID_TRIANGLE;
break;
}
InvalidateRect(hwnd,NULL,TRUE);
}

IV. Code complet▲

resource.h :

Sélectionnez

#define IDM_QUIT 1
#define IDM_PROP 2
#define ID_RB1 101
#define ID_RB2 102
#define ID_RB3 103
#define ID_CARRE 104
#define ID_CERCLE 105
#define ID_TRIANGLE 106

resource.rc :

Sélectionnez

#include <windows.h>
#include "resource.h"
LEMENU MENU
BEGIN
POPUP "Fichier"
BEGIN
MENUITEM "Propriétés...", IDM_PROP
MENUITEM SEPARATOR
MENUITEM "Quitter", IDM_QUIT
END
END
DIALOG1 DIALOG
60, 70, 175, 80
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Propriétés de la figure"
BEGIN
GROUPBOX " Forme ", -1, 20, 10, 60 , 60, WS_GROUP
AUTORADIOBUTTON "Carré", ID_CARRE, 30, 20, 40, 15,
AUTORADIOBUTTON "Cercle", ID_CERCLE, 30, 35, 40, 15,
AUTORADIOBUTTON "Triangle", ID_TRIANGLE, 30, 50, 40, 15,
GROUPBOX " Trait ", -1, 96, 10, 60 , 60, WS_GROUP
AUTORADIOBUTTON "Fin", ID_RB1, 106, 20, 40, 15
AUTORADIOBUTTON "Moyen", ID_RB2, 106, 35, 40, 15
AUTORADIOBUTTON "Large", ID_RB3, 106, 50, 40, 15
END

winmain.c :

Sélectionnez

#include <windows.h>
#include "resource.h"
#define DB_OK 1
#define FIN 1
#define MOYEN 2
#define LARGE 4
UINT Forme;
UINT Trait;
HINSTANCE hinst;
HWND hwnd;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY Dialog1Proc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{

MSG msg;
WNDCLASS wc;
hinst = hinstance;
wc.style = 0;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wc.lpszMenuName = "LEMENU";
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hwnd = CreateWindow("MaWinClass", "Check Box", WS_OVERLAPPED | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 260,
NULL, NULL, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hdlg;
switch (uMsg)
{
case WM_CREATE:
Forme = ID_CARRE;
Trait = FIN;
return 0;
case WM_COMMAND:
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(LOWORD(wParam) == IDM_PROP)
{
if(hdlg) ShowWindow(hdlg,SW_SHOWNORMAL);
else hdlg = CreateDialog(hinst, "DIALOG1" , hwnd,
(DLGPROC)Dialog1Proc);
}
return 0;
case WM_PAINT :
{
PAINTSTRUCT ps;
HDC hdc ;
HPEN hpen, hpOld;
POINT ptTriangle[3];
ptTriangle[0].x = 20;
ptTriangle[0].y = 20;
ptTriangle[1].x = 20;
ptTriangle[1].y = 170;
ptTriangle[2].x = 170;
ptTriangle[2].y = 95;
hdc = BeginPaint(hwnd, &ps);
hpen = CreatePen(PS_SOLID, Trait, 0);
hpOld = SelectObject(hdc,hpen);
if(Forme == ID_CARRE) Rectangle(hdc, 20, 20, 170, 170);
if(Forme == ID_CERCLE) Ellipse(hdc, 20, 20, 170, 170);
if(Forme == ID_TRIANGLE) Polygon(hdc, ptTriangle, 3);
SelectObject(hdc,hpOld);
DeleteObject(hpen);
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
/******************************************************************************/
BOOL APIENTRY Dialog1Proc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
{
if(Trait == FIN ) CheckDlgButton(hDlg, ID_RB1, BST_CHECKED);
if(Trait == MOYEN) CheckDlgButton(hDlg, ID_RB2, BST_CHECKED);
if(Trait == LARGE) CheckDlgButton(hDlg, ID_RB3, BST_CHECKED);
CheckRadioButton(hDlg, ID_CARRE, ID_TRIANGLE, Forme);
return TRUE;
}
case WM_COMMAND:
if (HIWORD(wParam) == BN_CLICKED) {
switch (LOWORD(wParam)) {
case ID_RB1:
Trait = FIN;
break;
case ID_RB2:
Trait = MOYEN;
break;
case ID_RB3:
Trait = LARGE;
break;
case ID_CARRE:
Forme = ID_CARRE;
break;
case ID_CERCLE:
Forme = ID_CERCLE;
break;
case ID_TRIANGLE:
Forme = ID_TRIANGLE;
break;
}
InvalidateRect(hwnd,NULL,TRUE);
}
if (LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg,0);
return TRUE;
}
default:
return FALSE;
}
}

J'ai testé les compilations avec C++ Builder et DevC++.

À vos PC.


39 - Windows GDI Tracés et découpes de contours.htm

I. Introduction

Dans cet article nous allons voir à l'aide d'un exemple comment tracer le contour d'un texte (contour des lettres), faire une découpe de ce contour et dessiner sur la découpe.

Image non disponible

Pour arriver à nos fins, nous utiliserons les fonctions de Paths et de Clipping du GDI de Windows. Le langage choisi sera le C et nous utiliserons l'API Windows pour que notre exemple soit compatible avec la plupart des compilateurs C et C++. Personnellement je l'ai compilé sur BCB4, BCB6 et Dev C++.
Dans cet article nous ne reviendrons pas sur l'architecture des applications Windows, pour cela vous pouvez aller consulter ce tutoriel : API Windows : La fenêtre principale. .

II. Explication

Nous créons donc une application traditionnelle Windows avec sa fonction d'entrée WinMain, nous y créons une fenêtre et sa procédure de fenêtre nommée pour l'occasion WndProc. Voir code complet en bas de ce document.

Nous allons nous concentrer sur le code exécuté à la réception du message WM_PAINT (je l'ai mis dans un if au lieu de l'habituel switch case juste pour des raisons de clarté du code).

Tout d'abord nous récupérons un contexte de périphérique HDC à l'aide de la fonction BeginPaint. Le dessin sur ce contexte sera réellement effectif lors de l'appel de la fonction EndPaint. (BeginPaint et EndPaint ne peuvent s'utiliser uniquement que dans le traitement d'un message WM_PAINT mais sont plus performantes que de dessiner sur un contexte de périphérique obtenu avec GetDC.)

Sélectionnez

PAINTSTRUCT ps;
HDC hdc;
hdc = BeginPaint(hwnd, &ps);
//Fonction de dessin ici
EndPaint(hwnd, &ps);

Ensuite nous créons une fonte à l'aide de la fonction CreateFontIndirect qui utilise une structure LOGFONT initialisée avec les caractéristiques de la fonte dont nous mettrons la taille suffisamment importante pour la visibilité dans l'exemple. Puis nous sélectionnons cette fonte pour le contexte de périphérique précédemment obtenu.

Sélectionnez

LOGFONT lf;
HFONT hfont;
HFONT hfontOld;
char f[32]={"Arial Black"};
ZeroMemory(&lf, sizeof(LOGFONT));
strcpy(lf.lfFaceName,f);
lf.lfHeight = 100;
hfont = CreateFontIndirect(&lf);
hfontOld = (HFONT)SelectObject(hdc, hfont);
//Utilisation ici
SelectObject(hdc, hfontOld);
DeleteObject(hfont);

Même opération pour un crayon dont nous initialiserons la taille à 3 pixels. (Ne pas oublier de détruire les objets de dessin quand ils ne sont plus nécessaires.)

Sélectionnez

HPEN hpen;
HPEN hpenOld;
hpen = CreatePen(PS_SOLID, 3, 0);
hpenOld = (HPEN)SelectObject(hdc, hpen);
//Utilisation ici
SelectObject(hdc, hpenOld);
DeleteObject(hpen);

Dessin du chemin :
Un chemin se prépare avant d'être dessiné, entre les fonctions BeginPathet EndPath qui reçoivent en paramètre un contexte de périphérique. (Entre ces deux fonctions, on peut utiliser toutes les fonctions de dessin du GDI.) Dans cet exemple nous dessinons donc un texte avec la fonction TextOut. C'est la fonction StrokePath qui dessine réellement le tracé préparé entre BeginPath et EndPath.

Sélectionnez

BeginPath(hdc);
TextOut(hdc, 10, 10, "Bonjour", 7);
EndPath(hdc);
StrokePath(hdc);

Nous aurions pu aussi utiliser la fonction FillPath qui peut remplir l'intérieur du chemin avec la brosse courante ou StrokeAndFillPath qui elle fait les deux. Quand vous avez appelé l'une de ces fonctions, le chemin n'est plus utilisable. Un tracé qui n'est pas fermé peut être fermé avec la fonction CloseFigure.

Remplissage d'une découpe :
Pour faire une découpe, il faut d'abord tracer son contour comme vu précédemment. Mais au lieu de le dessiner, nous le découperons avec la fonction SelectClipPath.

Sélectionnez

BeginPath(hdc);
TextOut(hdc, 10, 10, "Bonjour", 7);
EndPath(hdc);
SelectClipPath(hdc, RGN_XOR);

La découpe se trouve maintenant en sorte qu'elle ne fait plus partie du contexte de périphérique. Donc tout dessin fait sur ce contexte de périphérique ne sera pas tracé sur la découpe. La fonction SelectClipPath a plusieurs possibilités pour la découpe selon la valeur de son deuxième argument : RGN_AND, RGN_COPY, RGN_DIFF, RGN_OR, RGN_XOR.
Pour vous démontrer ceci, nous allons dessiner des traits horizontaux sur la surface de dessin dans une boucle.

Sélectionnez

SIZE sz;
int i;
GetTextExtentPoint32(hdc, "Bonjour",7 , &sz);
for (i = 121; i < (120 + sz.cy); i += 6) {
MoveToEx(hdc, 11, i, (LPPOINT) NULL);
LineTo(hdc, (9 + sz.cx), i);
}

La fonction GetTextExtentPoint32 sert ici à récupérer les dimensions du rectangle qui contient le texte préalablement tracé. Les traits seront donc dessinés seulement dans ce rectangle ceci juste dans un but d'esthétique.

III. Code de l'exemple

Sélectionnez

#include <windows.h>
HWND hwndMain ;
LONG APIENTRY WndProc(HWND hwnd, UINT uMsg, UINT wParam, LONG lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
WNDCLASS wc;
// Enregistrement de la classe de fenêtre.
wc.style = 0;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon((HINSTANCE) NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = "MainWndClass";
if (!RegisterClass(&wc)) return FALSE;
// Création de la fenêtre.
hwndMain = CreateWindow("MainWndClass", "Path and Clip",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
360, 280, NULL, NULL, hInstance, NULL);
ShowWindow(hwndMain, nCmdShow);
UpdateWindow(hwndMain);

// Et la fameuse boucle de messages
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LONG APIENTRY WndProc(HWND hwnd, UINT uMsg, UINT wParam, LONG lParam)
{
if (uMsg == WM_PAINT)
{
LOGFONT lf;
PAINTSTRUCT ps;
HFONT hfont;
HFONT hfontOld;
HPEN hpen;
HPEN hpenOld;
SIZE sz;
int i;
HDC hdc;
char f[32]={"Arial Black"};
hdc = BeginPaint(hwnd, &ps);
// Initialisation de la structure LOGFONT
ZeroMemory(&lf, sizeof(LOGFONT));
strcpy(lf.lfFaceName,f);
lf.lfHeight = 100;

// Céation de la fonte
hfont = CreateFontIndirect(&lf);
hfontOld = (HFONT)SelectObject(hdc, hfont);

// Création du crayon
hpen = CreatePen(PS_SOLID, 3, 0);
hpenOld = (HPEN)SelectObject(hdc, hpen);
// Dessin du chemin
BeginPath(hdc);
TextOut(hdc, 10, 10, "Bonjour", 7);
EndPath(hdc);
StrokePath(hdc);

// Dessin dans la découpe du chemin
BeginPath(hdc);
TextOut(hdc, 10, 120, "Bonjour", 7);
EndPath(hdc);
SelectClipPath(hdc, RGN_XOR);

GetTextExtentPoint32(hdc, "Bonjour",7 , &sz);
for (i = 121; i < (120 + sz.cy); i += 6) {
MoveToEx(hdc, 11, i, (LPPOINT) NULL);
LineTo(hdc, (9 + sz.cx), i);
}
SelectObject(hdc, hfontOld);
SelectObject(hdc, hpenOld);
DeleteObject(hpen);
DeleteObject(hfont);
EndPaint(hwnd, &ps);
return 0;
}
if (uMsg == WM_DESTROY)
{
PostQuitMessage(0);
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Avec la contribution de Demco pour la relecture.

Bon découpage.

À vos PC.


40 - Le TrayIcon_.htm

Avec cette application minimum, nous allons voir comment mettre une icône dans la barre des taches (à coté de l'horloge). Et comment faire surgir un menu quand on fait un click droit sur cette icône. Son fonctionnement sera le suivant : L'application s'ouvre normalement, puis si on click sur le bouton de fermeture de la fenêtre, la fenêtre devient invisible et l'icône apparait dans la barre des tâches. Pour fermer l'application il faudra cliquer sur le menu "Quitter" qui apparait au click droit sur l'icône. Pour la restaurer il faudra soit faire un simple click gauche sur l'icône soit utiliser le menu avec l'option "Restaurer".

II. Description sommaire :▲

Pour faire apparaître l'icone il faut remplir une structure de type NOTIFYICONDATA ensuite il faut appeller la fonction Shell_NotifyIcon avec comme paramètres la constante NIM_ADD et l'adresse de la variable de type NOTIFYICONDATA.

Sélectionnez

NOTIFYICONDATA TrayIcon;

ZeroMemory(&TrayIcon, sizeof(NOTIFYICONDATA));

TrayIcon.cbSize = sizeof(NOTIFYICONDATA);

TrayIcon.hWnd = hwnd;

TrayIcon.uID = 0;

TrayIcon.hIcon = LoadIcon(NULL, IDI_WINLOGO);

TrayIcon.uCallbackMessage = MY_WM_NOTIFYICON;

TrayIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;

strcpy(TrayIcon.szTip, "Test TrayIcon");

Shell_NotifyIcon(NIM_ADD,&TrayIcon);

La stucture NOTIFYICONDATA à un champ nommé uCallbackMessage qui doit être rempli avec un identificateur de message que l'on doit créer nous même.

Sélectionnez

#define MY_WM_NOTIFYICON WM_USER+1

Nous traiterons ce message dans la procédure de fenêtre comme n'importe quelle autre message. Dans l'exemple ce sera le click gauche pour faire réapparaître la fenêtre et le click droit pour fair surgir le menu. La suppréssion de l'icône ce fait aussi avec la fonction Shell_NotifyIcon.

III. Code complet :▲

III-A. resources.h :▲

Sélectionnez

#define IDM_QUIT 1

#define IDM_RESTORE 2

III-B. resources.rc :▲

Sélectionnez

#include <windows.h>

#include "resources.h"

LEMENU MENU

BEGIN

POPUP "TrayMenu"

BEGIN

MENUITEM "Restorer", IDM_RESTORE

MENUITEM SEPARATOR

MENUITEM "Quitter", IDM_QUIT

END

END

III-C. winmain.c :▲

Sélectionnez

#include <windows.h>

#include "resources.h"

#define MY_WM_NOTIFYICON WM_USER+1

NOTIFYICONDATA TrayIcon;

HINSTANCE hinst;

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

HWND hwnd;

MSG msg;

WNDCLASS wc;

hinst = hinstance;

wc.style = 0;

wc.lpfnWndProc = MainWndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hinstance;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);

wc.lpszMenuName = NULL;

wc.lpszClassName = "MaWinClass";

if(!RegisterClass(&wc)) return FALSE;

hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,

NULL, NULL, hinstance, NULL);

if (!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow);

UpdateWindow(hwnd);

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

/****************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

switch (uMsg)

{

case WM_CREATE:

return 0;

case WM_CLOSE:

ShowWindow(hwnd,SW_HIDE);

TrayIcon.cbSize = sizeof( NOTIFYICONDATA );

TrayIcon.hWnd = hwnd;

TrayIcon.uID = 0;

TrayIcon.hIcon = LoadIcon(NULL, IDI_WINLOGO);

TrayIcon.uCallbackMessage = MY_WM_NOTIFYICON;

TrayIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;

strcpy(TrayIcon.szTip, "Test trayicon");

Shell_NotifyIcon(NIM_ADD,&TrayIcon);

return 0;

case MY_WM_NOTIFYICON :

if(lParam == WM_LBUTTONUP)

{

ShowWindow(hwnd,SW_SHOW);

Shell_NotifyIcon(NIM_DELETE,&TrayIcon);

}

if(lParam == WM_RBUTTONUP)

{

HMENU hmenu;

HMENU hpopup;

POINT pos;

GetCursorPos(&pos);

hmenu = LoadMenu(hinst,"LEMENU");

hpopup = GetSubMenu(hmenu, 0);

SetForegroundWindow(hwnd);

TrackPopupMenuEx(hpopup, 0, pos.x, pos.y, hwnd, NULL);

DestroyMenu(hmenu);

}

return 0;

case WM_COMMAND:

if(LOWORD(wParam) == IDM_QUIT) DestroyWindow(hwnd);

if(LOWORD(wParam) == IDM_RESTORE)

{

ShowWindow(hwnd,SW_SHOW);

Shell_NotifyIcon(NIM_DELETE,&TrayIcon);

}

return 0;

case WM_DESTROY:

Shell_NotifyIcon(NIM_DELETE,&TrayIcon);

PostQuitMessage(0);

return 0;

default:

return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

}

J'ai testé les compilations avec C++ Builder et DevC++.
A vos PC.


41 - Énumération de ressources.htm

Énumération de ressources

I. Introduction

Voici un exemple d'énumération de ressources. Dans cet exemple nous allons afficher dans une ListView toutes les icônes de la dll "moricons.dll". C'est la fonction EnumResourceNames qui lance l'énumération. Les ressources peuvent être récupérées dans une fonction CALLBACK, nommée ResNameProc dans l'exemple.

II. Code complet

winmain.c

Sélectionnez

#include <windows.h>

#include <commctrl.h>

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

BOOL CALLBACK ResNameProc(HANDLE, LPCTSTR, LPTSTR, LONG);

HINSTANCE hinst;

HWND hListView;

HIMAGELIST himgList;

LV_ITEM lvi;

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

HWND hwnd;

MSG msg;

WNDCLASS wc;

hinst = hinstance;

wc.style = 0 ;

wc.lpfnWndProc = MainWndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hinstance;

wc.hIcon = NULL;

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = NULL;

wc.lpszMenuName = NULL;

wc.lpszClassName = "MaWinClass";

if(!RegisterClass(&wc)) return FALSE;

hwnd = CreateWindow("MaWinClass", "List View", WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,

NULL, NULL, hinstance, NULL);

if (!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow);

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

switch (uMsg)

{

case WM_CREATE:

{

HINSTANCE dllhinst;

HICON hIcon;

InitCommonControls();

himgList = ImageList_Create(GetSystemMetrics(SM_CXICON),

GetSystemMetrics(SM_CYICON), ILC_COLOR32 , 3, 3);

ImageList_SetBkColor(himgList, GetSysColor(COLOR_WINDOW));

ZeroMemory(&lvi, sizeof(LV_ITEM));

lvi.mask = LVIF_TEXT | LVIF_IMAGE ;

hListView =CreateWindowEx(WS_EX_CLIENTEDGE , WC_LISTVIEW, "",

WS_CHILD | WS_VISIBLE , 0, 0, 0, 0, hwnd, NULL, hinst, NULL);

ListView_SetImageList(hListView, himgList, LVSIL_NORMAL);

dllhinst = LoadLibrary("moricons.dll");

EnumResourceNames(dllhinst, RT_GROUP_ICON,

(ENUMRESNAMEPROC)ResNameProc,NULL);

FreeLibrary(dllhinst);

return 0;

}

case WM_SIZE:

MoveWindow(hListView, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);

ListView_Arrange(hListView,LVA_DEFAULT);

return 0;

case WM_DESTROY:

PostQuitMessage(0);

return 0;

default:

return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

}

BOOL CALLBACK ResNameProc(HANDLE hModule, LPCTSTR lpszType,LPTSTR lpszName,

LONG lParam)

{

if (IS_INTRESOURCE(lpszName))

{

HICON hIcon;

char st[256];

hIcon = LoadIcon(hModule, MAKEINTRESOURCE((USHORT)lpszName));

ImageList_AddIcon(himgList, hIcon);

wsprintf(st,"Icône %d",(USHORT)lpszName);

lvi.pszText = st;

lvi.iImage = (USHORT)lpszName;

lvi.iItem = (USHORT)lpszName;

ListView_InsertItem(hListView, &lvi);

}

else

{

/* icones nommées */

}

return TRUE;

}

J'ai testé les compilations avec C++ Builder et DevC++.


42 - Afficher des images sous windows NT.htm

Afficher des images sous windows NT

I. Introduction

Nous avons vu comment afficher des images bitmap tout en les chargeant avec la fonction LoadImage, hélas cette fonction ne permet pas de charger une image depuis un fichier sous Windows NT. Nous allons devoir charger le fichier et le traiter avant affichage.

II. Chargement du fichier

L'ouverture du fichier se fait avec la fonction CreateFile. Nous extrayons tout d'abord l'entête du fichier BITMAPFILEHEADER à l'aide de la fonction ReadFile. Ensuite nous chargeons le bitmap dans un buffer.

Sélectionnez

static LPVOID buffer;

/* ... */

HANDLE hf;

DWORD FileSize,nbcharRead;

BITMAPFILEHEADER bfh;

hf = CreateFile("image.bmp", GENERIC_READ, 0,NULL,

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

FileSize = GetFileSize(hf, NULL);

ReadFile(hf, &bfh, sizeof(bfh), &nbcharRead, NULL) ;

buffer = GlobalAlloc(GMEM_FIXED, FileSize);

ReadFile(hf, buffer, FileSize - sizeof(BITMAPFILEHEADER),

&nbcharRead, NULL);

/* ... */

CloseHandle(hf);

/* ... */

GlobalFree(buffer);

III. Création de la palette de couleur

Si le bitmap à un nombre de couleurs inférieur ou égal à 256, il faut générer sa palette de couleur. Les bitmaps ayant plus de 256 couleurs n'ont pas de palette. Chaque pixel est représenté par la valeur réelle de sa couleur. Le bitmap contient un entête BITMAPINFO donnant ces informations nous y extrayons le nombre de couleurs et créons la palette si besoin. La palette sera ensuite accessible via son handle de palette hpal de type HPALETTE.

Sélectionnez

static LPVOID buffer;

static UINT nbColors;

static HPALETTE hpal;

static BITMAPINFO *bmInfo;

/* ... */

bmInfo = (BITMAPINFO*)buffer;

nbColors = bmInfo->bmiHeader.biClrUsed ?

bmInfo->bmiHeader.biClrUsed :

1 << bmInfo->bmiHeader.biBitCount;

if( nbColors <= 256 )

{

UINT i;

UINT nSize = sizeof(LOGPALETTE) +

(sizeof(PALETTEENTRY) * nbColors);

LOGPALETTE *pLPal = (LOGPALETTE*) GlobalAlloc(GMEM_FIXED,nSize);

pLPal->palVersion = 0x300;

pLPal->palNumEntries = nbColors;

for( i=0; i < nbColors; i++)

{

pLPal->palPalEntry[i].peRed = bmInfo->bmiColors[i].rgbRed;

pLPal->palPalEntry[i].peGreen = bmInfo->bmiColors[i].rgbGreen;

pLPal->palPalEntry[i].peBlue = bmInfo->bmiColors[i].rgbBlue;

pLPal->palPalEntry[i].peFlags = 0;

}

hpal = CreatePalette(pLPal);

GlobalFree(pLPal);

}

IV. Affichage de l'image

L'affichage de l'image se fait avec la fonction SetDIBitsToDevice. Cette fonction reçoit entre autres un pointeur sur l'entête du bitmap (bmInfo) mais aussi un pointeur sur les pixels (lpDIBBits) qu'il faut calculer en fonction des infos contenues dans l'entête du bitmap. Et s'il y a une palette, il faut la sélectionner.

Sélectionnez

static LPVOID buffer;

static UINT nbColors;

static HPALETTE hpal;

static BITMAPINFO *bmInfo;

/* ... */

case WM_PAINT :

{

HDC hdc;

PAINTSTRUCT ps;

LPVOID lpDIBBits;

if( bmInfo->bmiHeader.biBitCount > 8 )

lpDIBBits = (bmInfo->bmiColors +

bmInfo->bmiHeader.biClrUsed) +

((bmInfo->bmiHeader.biCompression == BI_BITFIELDS) ? 3 : 0);

else

lpDIBBits = bmInfo->bmiColors + nbColors;

hdc = BeginPaint(hwnd, &ps);

if( hpal && (GetDeviceCaps(hdc,RASTERCAPS) & RC_PALETTE) )

{

SelectPalette(hdc, hpal, FALSE);

RealizePalette(hdc);

}

SetDIBitsToDevice(hdc,10,10,bmInfo->bmiHeader.biWidth,

bmInfo->bmiHeader.biHeight, 0, 0, 0,

bmInfo->bmiHeader.biHeight, lpDIBBits, bmInfo,

DIB_RGB_COLORS);

EndPaint(hwnd, &ps);

return 0;

}

V. Code complet

L'exemple pour rester simple n'effectue aucun test de validité. Il va de soi que dans un cas réel il faudra effectuer ces tests.

winmain.c

Sélectionnez

#include <windows.h>

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

HWND hwnd;

MSG msg;

WNDCLASS wc;

wc.style = 0;

wc.lpfnWndProc = MainWndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hinstance;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);

wc.lpszMenuName = NULL;

wc.lpszClassName = "MaWinClass";

if(!RegisterClass(&wc)) return FALSE;

hwnd = CreateWindow("MaWinClass", "Dessiner", WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, 280, 240,

NULL, NULL, hinstance, NULL);

if (!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow);

UpdateWindow(hwnd);

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

static LPVOID buffer;

static BITMAPINFO *bmInfo;

static UINT nbColors;

static HPALETTE hpal;

switch (uMsg)

{

case WM_CREATE :

{

HANDLE hf;

DWORD FileSize,nbcharRead;

BITMAPFILEHEADER bfh;

hf = CreateFile("image.bmp", GENERIC_READ, 0,NULL,

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

FileSize = GetFileSize(hf, NULL);

ReadFile(hf, &bfh, sizeof(bfh), &nbcharRead, NULL) ;

buffer = GlobalAlloc(GMEM_FIXED, FileSize);

ReadFile(hf, buffer, FileSize - sizeof(BITMAPFILEHEADER),

&nbcharRead, NULL);

bmInfo = (BITMAPINFO*)buffer;

nbColors = bmInfo->bmiHeader.biClrUsed ?

bmInfo->bmiHeader.biClrUsed :

1 << bmInfo->bmiHeader.biBitCount;

if( nbColors <= 256 )

{

UINT i;

UINT nSize = sizeof(LOGPALETTE) +

(sizeof(PALETTEENTRY) * nbColors);

LOGPALETTE *pLPal = (LOGPALETTE*) GlobalAlloc(GMEM_FIXED,nSize);

pLPal->palVersion = 0x300;

pLPal->palNumEntries = nbColors;

for( i=0; i < nbColors; i++)

{

pLPal->palPalEntry[i].peRed = bmInfo->bmiColors[i].rgbRed;

pLPal->palPalEntry[i].peGreen = bmInfo->bmiColors[i].rgbGreen;

pLPal->palPalEntry[i].peBlue = bmInfo->bmiColors[i].rgbBlue;

pLPal->palPalEntry[i].peFlags = 0;

}

hpal = CreatePalette(pLPal);

GlobalFree(pLPal);

}

CloseHandle(hf);

return 0;

}

case WM_PAINT :

{

HDC hdc;

PAINTSTRUCT ps;

LPVOID lpDIBBits;

if( bmInfo->bmiHeader.biBitCount > 8 )

lpDIBBits = (bmInfo->bmiColors +

bmInfo->bmiHeader.biClrUsed) +

((bmInfo->bmiHeader.biCompression == BI_BITFIELDS) ? 3 : 0);

else

lpDIBBits = bmInfo->bmiColors + nbColors;

hdc = BeginPaint(hwnd, &ps);

if( hpal && (GetDeviceCaps(hdc,RASTERCAPS) & RC_PALETTE) )

{

SelectPalette(hdc, hpal, FALSE);

RealizePalette(hdc);

}

SetDIBitsToDevice(hdc,10,10,bmInfo->bmiHeader.biWidth,

bmInfo->bmiHeader.biHeight, 0, 0, 0,

bmInfo->bmiHeader.biHeight, lpDIBBits, bmInfo,

DIB_RGB_COLORS);

EndPaint(hwnd, &ps);

return 0;

}

case WM_DESTROY :

GlobalFree(buffer);

PostQuitMessage(0);

return 0;

default :

return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

}

J'ai testé les compilations avec C++ Builder et DevC++.


43 - Les services NT.htm

Les services NT.

Introduction :

Dans ce chapitre, nous allons aborder les services NT.
Un service est une petite application qui travaille souvent en tache de fond. Les applications services sont enregistrées dans une base de données et gérées par une application le "Service Control Manager". Elles peuvent être lancées automatiquement au démarrage du système ou manuellement par une autre application ou un autre service.

Installation d'un service :

Avant d'être utilisé un service doit être installé. C'est à dire enregistré par le système. C'est le rôle de la fonction CreateService qui reçoit notamment comme paramètres le nom d'enregistrement du service, "MonService" dans l'exemple. Il est enregistré sous forme d'une chaîne de caractère. C'est ce nom qui nous servira à accéder ultérieurement à ce service. Nous avons comme autre paramètre le libellé du service et diverses constantes lui affectant certaines caractéristiques (voir aide API Win32). On peut remarquer que l'on fournie aussi comme paramètre une chaîne de caractère contenant le chemin et le nom de l'exécutable. C'est évidement nécessaire au système pour le localiser. Toutes ces données sont enregistrées dans le registre.

#include "Windows.h"

#include "Winsvc.h"

int main()

{

char strDir[1024];

SC_HANDLE hSCManager,hService;

SERVICE_DESCRIPTION SD;

GetCurrentDirectory(1024,strDir);

strcat(strDir,"\\MonService.exe");

hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

hService = CreateService(hSCManager, "MonService", "Mon Service",

SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START,

SERVICE_ERROR_NORMAL, strDir, NULL, NULL, NULL, NULL, NULL);

SD.lpDescription = "Ici la description de mon service.";

ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &SD);

CloseServiceHandle(hService);

return 0;

}

Désinstallation d'un service :

Voyons brièvement sa désinstallation. Elle s'effectue avec la fonctionDeleteService après avoir ouvert le service avec OpenService.

#include "Windows.h"

#include "Winsvc.h"

int main()

{

SC_HANDLE hSCManager;

SC_HANDLE hServ;

hSCManager = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);

hServ=OpenService(hSCManager,"MonService",SERVICE_ALL_ACCESS);

DeleteService(hServ);

CloseServiceHandle(hServ);

CloseServiceHandle(hSCManager);

return 0;

}

Le service :

Au démarrage d'un service l'exécutable est lancé et sa fonction main exécutée. Dans cette fonction main nous devons remplir un tableau de structure SERVICE_TABLE_ENTRY. Une case du tableau par service (un exécutable peut contenir plusieurs services). Le premier champ de la structure contient la chaîne de caractères identifiant le service. Le deuxième contient un pointeur de fonction sur la fonction principale du service. La dernière structure du tableau doit avoir ses champs à NULL.

Voyons maintenant la fonction d'exécution du service, nommée "ServiceMain" dans l'exemple. Dans cette fonction nous remplissons la structure ServiceStatus de type SERVICE_STATUS. Cette structure prepare l'état que l'on veux donner au service pendant son exécution.
Nous devons aussi implémenter une autre fonction nommée "ServCtrlHandler" dans l'exemple, dont on fournira un pointeur à la fonction RegisterServiceCtrlHandler afin de l'enregistrer. Le Service Control Manager pourra alors lui envoyer des ordres, que cette fonction devra traiter.
Les changements d'état affectés en cours d'exécution dans la structure ServiceStatus doivent être validé par l'appel de la fonction SetServiceStatus. Son premier paramètre est un handle renvoyé par la fonction RegisterServiceCtrlHandler et le second est l'adresse de la structure ServiceStatus. Ensuite on entre dans une boucle qui s'exécute tant que le service est démarré. C'est dans cette boucle que l'on implémente les actions que l'on désire exécuter.

#include "Windows.h"

#include "Winsvc.h"

SERVICE_STATUS ServiceStatus;

SERVICE_STATUS_HANDLE hServiceStatus;

BOOL ServiceStarted=TRUE;

void WINAPI ServiceMain(DWORD argc, LPTSTR *argv);

void WINAPI ServCtrlHandler(DWORD Opcode);

int main()

{

SERVICE_TABLE_ENTRY Table[] = {{"Service1",ServiceMain},{NULL,NULL}};

StartServiceCtrlDispatcher(Table);

return 0;

}

/******************************************************************************/

void WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)

{

ZeroMemory(&ServiceStatus, sizeof(SERVICE_STATUS));

ServiceStatus.dwServiceType = SERVICE_WIN32;

ServiceStatus.dwCurrentState = SERVICE_START_PENDING;

ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;

hServiceStatus = RegisterServiceCtrlHandler("MonService", ServCtrlHandler);

ServiceStatus.dwCurrentState = SERVICE_RUNNING;

ServiceStatus.dwCheckPoint = 0;

ServiceStatus.dwWaitHint = 0;

SetServiceStatus (hServiceStatus, &ServiceStatus);

ServiceStarted=TRUE;

while(ServiceStarted)

{

Sleep(5000);

/* Ici la tache à éffectuer. */

}

}

/******************************************************************************/

void WINAPI ServCtrlHandler(DWORD SCCode)

{

switch(SCCode)

{

case SERVICE_CONTROL_CONTINUE:

ServiceStatus.dwCurrentState = SERVICE_RUNNING;

SetServiceStatus (hServiceStatus,&ServiceStatus);

return;

case SERVICE_CONTROL_PAUSE:

ServiceStatus.dwCurrentState = SERVICE_PAUSED;

SetServiceStatus (hServiceStatus,&ServiceStatus);

return;

case SERVICE_CONTROL_STOP:

ServiceStatus.dwWin32ExitCode = 0;

ServiceStatus.dwCurrentState = SERVICE_STOPPED;

ServiceStatus.dwCheckPoint = 0;

ServiceStatus.dwWaitHint = 0;

SetServiceStatus (hServiceStatus,&ServiceStatus);

ServiceStarted=FALSE;

return;

}

}

Démarrer un service :

Un service peut ce démarrer automatiquement ou manuellement selon sa configuration. Pour le démarrer manuellement par programme, il suffit ouvrir le service et de le démarrer avec la fonction StartService.

void StartService()

{

SC_HANDLE SCMan;

SCMan = OpenSCManager(NULL, NULL, GENERIC_EXECUTE) ;

SC_HANDLE MonServ;

MonServ = OpenService(SCMan, "MonService", SERVICE_START );

StartService(MonServ, 0, NULL);

CloseServiceHandle(MonServ);

CloseServiceHandle(SCMan);

}

Arrêter un service :

Un service peut de même être arrêté par programme à l'aide de la fonction ControlService. A savoir que si des services dépendent de votre service, ils doivent être arrêtés avant l'arrêt de votre service.

void StopService()

{

SERVICE_STATUS_PROCESS ssp;

SC_HANDLE SCMan;

SCMan = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS) ;

SC_HANDLE MonServ;

MonServ = OpenService(SCMan, "MonService", SERVICE_STOP );

ControlService( MonServ, SERVICE_CONTROL_STOP, (LPSERVICE_STATUS) &ssp ) ;

CloseServiceHandle(MonServ);

CloseServiceHandle(SCMan);

}

J'ai omis pour faciliter la comphéantion de tester la validité des valeurs retournées par certaine des fonctions de cet exemple.
J'ai testé les compilations avec C++ Builder et DevC++.
N'hésitez pas à consulter la msdn pour de plus amples renseignements sur les services.

A vos PC.


44 - Api Windows Une seule instance pour l'application_.htm

Une seule instance pour l'application.

Introduction :

Nous allons voir comment n'ouvrir qu'une seule instance pour une application Win32. Nous utiliserons pour cela les Mutex.

Description :

Pour arriver à nos fins nous créons un Mutex avec un nom unique transmis dans le troisième paramètre de la fonction CreateMutex. Créer un autre Mutex dans le système avec ce nom provoquera une erreur (ERROR_ALREADY_EXISTS). En lancant une deuxième fois l'application, elle va tenter de créer à nouveau le mutex, ce qui provequera l'erreur que l'on traitera afin de fermer la deuxième instance que l'on a tenté d'ouvrir.

HANDLE hMutex;
hMutex = CreateMutex (NULL,FALSE,"Nom Unique du Mutex");
if (GetLastError() == ERROR_ALREADY_EXISTS) return 0;

Nous ferons donc ce test au tout debut du WinMain avant la création de la fenètre.

Code complet :

winmain.c :

#include <windows.h>
LONG APIENTRY WndProc(HWND hwnd, UINT uMsg, UINT wParam, LONG lParam);
HINSTANCE hInst;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwndMain ;
MSG msg;
WNDCLASS wc;
HANDLE hMutex;
hMutex = CreateMutex (NULL,FALSE,"Nom Unique du Mutex");
if (GetLastError() == ERROR_ALREADY_EXISTS) return 0;
hInst = hInstance;
wc.style = 0;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon((HINSTANCE) NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wc.lpszMenuName = MAKEINTRESOURCE(1); ;
wc.lpszClassName = "MainWndClass";
if (!RegisterClass(&wc)) return FALSE;
hwndMain = CreateWindow("MainWndClass", "WinApi",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
360, 280, NULL, NULL, hInstance, NULL);
ShowWindow(hwndMain, nCmdShow);
UpdateWindow(hwndMain);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LONG APIENTRY WndProc(HWND hwnd, UINT uMsg, UINT wParam, LONG lParam)
{
switch ( uMsg ) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

J'ai testé les compilations avec C++ Builder et DevC++.

A vos PC.

CGi


45 - Api Windows Démineur_.htm

Code d'un Démineur.

Introduction :

Code d'un démineur en pure Windows API.

Code complet :

#include <windows.h>
#include <time.h>
#include <stdio.h>
#define LARG 32
#define NBC_X 16
#define NBC_Y 12
#define NB_MINE 20
/* ID de menu */
#define IDM_QUIT 1
#define IDM_NEW 2
#define IDM_ABOUT 3
#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
int Demine(int tab[NBC_Y][NBC_X], int y, int x);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
HMENU hMenu, hSousMenu;
hinst = hinstance;
wc.style = 0;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hSousMenu = CreateMenu();
AppendMenu(hSousMenu, MF_STRING, IDM_NEW, "Nouvelle partie");
AppendMenu(hSousMenu, MF_MENUBREAK, -1, NULL);
AppendMenu(hSousMenu, MF_STRING, IDM_ABOUT, "A propos");
AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");
hMenu = CreateMenu();
AppendMenu(hMenu,MF_POPUP,(UINT)hSousMenu,"Fichier");
hwnd = CreateWindow("MaWinClass", "DMine",
WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT,
LARG*(NBC_X+1)-22, LARG*(NBC_Y+1)+20, NULL, hMenu, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
int Demine(int tab[NBC_Y][NBC_X], int y, int x)
{
if(tab[y][x]&0x00010000) tab[y][x]=tab[y][x]&0xFFFEFFFF;
else return 0;
if(tab[y][x]&0x100) return 1; /* Si mine --> Perdu */
if(tab[y][x]&0x0000000F) return 0; /* case comporte un nb */
if(y!=0&&x!=0) Demine(tab, y-1, x-1); /* traitement des cases adjacentes */
if(y!=0) Demine(tab, y-1, x);
if(y!=0&&x!=NBC_X-1) Demine(tab, y-1, x+1);
if(x!=0) Demine(tab, y, x-1);
if(x!=NBC_X-1) Demine(tab, y, x+1);
if(y!=NBC_Y-1&&x!=0) Demine(tab, y+1, x-1);
if(y!=NBC_Y-1) Demine(tab, y+1, x);
if(y!=NBC_Y-1&&x!=NBC_X-1) Demine(tab, y+1, x+1);
return 0;
}
/******************************************************************************/
void Showtab(int tab[NBC_Y][NBC_X])
{
int x, y;
for(y=0; y<NBC_Y; y++)
for(x=0; x<NBC_X; x++)
{
tab[y][x]=tab[y][x]&0xFFFEFFFF;
}
}
/******************************************************************************/
int Test(int tab[NBC_Y][NBC_X])
{
int x, y;
int result = 1;
for(y=0; y<NBC_Y; y++)
for(x=0; x<NBC_X; x++)
{
if(tab[y][x]&0x00010000 && !(tab[y][x]&0x00000100)) result=0;
}
return result;
}
/******************************************************************************/
void Init(int tab[NBC_Y][NBC_X])
{
int x, y;
for(x=0; x<NB_MINE; x++) /* Pose les mines */
{
int mineX, mineY;
mineX = rand()%NBC_X;
mineY = rand()%NBC_Y;
if(tab[mineY][mineX]&0x100) x--;
else tab[mineY][mineX] = 0x100;
}
for(y=0; y<NBC_Y; y++) /* Init les cases */
for(x=0; x<NBC_X; x++)
{
if(tab[y][x]&0x100)
{
if(y!=0&&x!=0) tab[y-1][x-1]++;
if(y!=0) tab[y-1][x]++;
if(y!=0&& x!=NBC_X-1) tab[y-1][x+1]++;
if(x!=0) tab[y][x-1]++;
if(x!=NBC_X-1) tab[y][x+1]++;
if(y!=NBC_Y-1&&x!=0) tab[y+1][x-1]++;
if(y!=NBC_Y-1) tab[y+1][x]++;
if(y!=NBC_Y-1&&x!=NBC_X-1) tab[y+1][x+1]++;
}
tab[y][x]=tab[y][x]|0x00010000;
}
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static int tab[NBC_Y][NBC_X];
static int end;
switch (uMsg)
{
case WM_CREATE:
srand((unsigned int)time(0));
Init(tab);
return 0;
case WM_COMMAND:
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(LOWORD(wParam) == IDM_ABOUT)
MessageBox(NULL, "Démineur par CGi !", "A Propos !", MB_OK);
if(LOWORD(wParam) == IDM_NEW)
{
ZeroMemory(tab, sizeof(tab));
Init(tab);
end=0;
InvalidateRect(hwnd, NULL, FALSE);
}
return 0;
case WM_LBUTTONUP:
{
int x, y;
int xPos = GET_X_LPARAM(lParam);
int yPos = GET_Y_LPARAM(lParam);
if(end) return 0;
x = xPos/LARG;
y = yPos/LARG;
InvalidateRect(hwnd, NULL, FALSE);
if(Demine(tab, y, x))
{
Showtab(tab);
MessageBox(NULL, "Désolé ! Vous avez Perdu !", "DMine", MB_OK);
end=1;
}
else
if(Test(tab))
{
MessageBox(NULL, "Bravo ! Vous avez Gagné !", "DMine", MB_OK);
end=1;
}
return 0;
}
case WM_PAINT:
{
int i, j;
HPEN hpen;
HDC hdc, hdcDB;
HBITMAP bmDB;
HGDIOBJ hobjold;
PAINTSTRUCT ps;
RECT rect, caserect;
HBRUSH hbcase = CreateSolidBrush(0x00CCEECC);
hpen = CreatePen(PS_SOLID, 1, 0x00000000);
GetClientRect(hwnd, &rect);
hdc = BeginPaint(hwnd, &ps);
hdcDB = CreateCompatibleDC(hdc);
bmDB = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
hobjold = SelectObject(hdcDB, bmDB);
SelectObject(hdcDB,hpen);
FillRect(hdcDB, &rect, (HBRUSH)(COLOR_3DFACE+1));
for(j=0; j<NBC_Y; j++)
for(i=0; i<NBC_X; i++)
{
int value = tab[j][i];
caserect.left = i*LARG+1;
caserect.right = i*LARG+LARG;
caserect.top = j*LARG+1;
caserect.bottom = j*LARG+LARG;
Rectangle(hdcDB, i*LARG+1, j*LARG+1,
i*LARG+LARG , j*LARG+LARG);
SetTextColor(hdcDB,0x00000000);
if(value&0x100) /* mine */
DrawText(hdcDB,"M",1,&caserect,
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
if(value>0 && value<9) /* les nombres */
{
char st[16];
sprintf(st, "%i", value);
if(value==1) SetTextColor(hdcDB,0x00FF0000);
if(value==2) SetTextColor(hdcDB,0x00008F00);
if(value==3) SetTextColor(hdcDB,0x000000FF);
if(value>=4) SetTextColor(hdcDB,0x00FF00FF);
DrawText(hdcDB, st, 1, &caserect,
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
if(value&0x00010000) /* cache les cases */
{
caserect.left++;
caserect.right--;
caserect.top++;
caserect.bottom--;
FillRect(hdcDB, &caserect, hbcase);
}
}
BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcDB, 0, 0, SRCCOPY);
DeleteDC(hdcDB);
DeleteObject(bmDB);
EndPaint(hwnd, &ps);
DeleteObject(hbcase);
DeleteObject(hpen);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

A vos compilateurs.

CGi


46 - Api Windows MasterMind_.htm

Code d'un MasterMind.

Introduction :

Code d'un Mastermind en pure Windows API.

Code complet :
#include <windows.h>
#include <stdlib.h>
#include <time.h>
#define LARG 32 // Largeur cases
#define ESP 8 // Espace entre cases
#define EP_TRAIS 2 // Epaisseur du trais
#define NB_COLOR 8 // 6 ou 8 couleurs
#define NB_CASE 4 // Nombre de cases
#define NB_LINE 10 // Nombre max d'essais
#define PAS (LARG+ESP)
#define HT_BTN PAS // Hauteur du bouton
// Position de la palette
#define POS_PAL_X ((NB_CASE+2)*PAS)
#define POS_PAL_Y PAS
// Position du tableau
#define POS_TAB_X PAS
#define POS_TAB_Y (PAS*(NB_LINE+1)+ESP)
// Position des indices
#define POS_IDC_X POS_PAL_X
#define POS_IDC_Y POS_TAB_Y
// Position des résultats
#define POS_RES_X POS_TAB_X
#define POS_RES_Y POS_PAL_Y
// ID de menu
#define IDM_QUIT 1
#define IDM_NEW 2
#define ID_BTN 3
#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
#if NB_COLOR != 6 && NB_COLOR != 8
#error NB_COLOR value must be 6 or 8 !
#endif
HINSTANCE hinst;
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
HMENU hMenu, hSousMenu;
hinst = hinstance;
wc.style = 0;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = "MaWinClass";
if(!RegisterClass(&wc)) return FALSE;
hSousMenu = CreateMenu();
AppendMenu(hSousMenu, MF_STRING, IDM_NEW, "Nouvelle partie");
AppendMenu(hSousMenu, MF_STRING, IDM_QUIT, "Quitter");
hMenu = CreateMenu();
AppendMenu(hMenu,MF_POPUP,(UINT)hSousMenu,"Fichier");
hwnd = CreateWindow("MaWinClass", "MasterMind",
WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT,
(NB_CASE+3)*PAS+NB_COLOR*PAS, (NB_LINE+3)*PAS+HT_BTN+36,
NULL, hMenu, hinstance, NULL);
if (!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/******************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hButton;
static int tab[NB_LINE][NB_CASE]; // Tableau
static int TabRand[NB_CASE]; // Tableau du tirage en cours
static int TabRed[NB_LINE]; // Tableau des indices rouge TabRes
static int TabWhite[NB_LINE]; // Tableau des indices blanc
static int CurentLine; // Ligne en cours
static HBRUSH color[NB_COLOR+2];
static int btndown; // Drag on
static int AffRes; // Affiche le résultat
static int xPos;
static int yPos;
static int SelColor; // Drag color
switch (uMsg)
{
case WM_CREATE :
{
int i;
color[0] = CreateSolidBrush(0x008F8F8F); //Cases inactives grises
color[1] = CreateSolidBrush(0x000000FF); // Les 6 couleurs
color[2] = CreateSolidBrush(0x0000FF00);
color[3] = CreateSolidBrush(0x00FF4F4F);
color[4] = CreateSolidBrush(0x00CF00CF);
color[5] = CreateSolidBrush(0x0000FFFF);
color[6] = CreateSolidBrush(0x00FFFF00);
#if(NB_COLOR==8)
color[7] = CreateSolidBrush(0x00008F00);
color[8] = CreateSolidBrush(0x00FFCFFF);
#endif //Cases actives blanches
color[NB_COLOR+1] = CreateSolidBrush(0x00FFFFFF);
hButton = CreateWindow("button", "Valider", WS_CHILD | WS_VISIBLE,
0, 0, 0, 0, hwnd, (HMENU)ID_BTN, hinst, NULL);
srand(time(0));
for(i=0; i<NB_CASE; i++)
{
TabRand[i] = rand()%NB_COLOR+1;
tab[CurentLine][i] = NB_COLOR+1;
}
return 0;
}
case WM_COMMAND:
if(LOWORD(wParam) == IDM_QUIT) PostMessage(hwnd, WM_CLOSE,0,0);
if(LOWORD(wParam) == IDM_NEW)
{
int i;
AffRes = 0;
CurentLine = 0;
EnableWindow(hButton, TRUE);
for(i=0; i<NB_CASE; i++) TabRand[i] = rand()%NB_COLOR+1;
ZeroMemory(tab, sizeof(tab));
ZeroMemory(TabRed, sizeof(TabRed));
ZeroMemory(TabWhite, sizeof(TabWhite));
for(i=0; i<NB_CASE; i++) tab[CurentLine][i] = NB_COLOR+1;
InvalidateRect(hwnd, NULL, FALSE);
}
if(LOWORD(wParam) == ID_BTN)
{
int i;
int TabCptR[NB_COLOR+2]={0};
int TabCptJ[NB_COLOR+2]={0};
for(i=0; i<NB_CASE; i++)
{
if (TabRand[i] == tab[CurentLine][i]) TabRed[CurentLine]++;
TabCptR[TabRand[i]]++;
TabCptJ[tab[CurentLine][i]]++;
}
for(i=1; i<NB_COLOR+1; i++)
TabWhite[CurentLine] += TabCptR[i] < TabCptJ[i]
? TabCptR[i] : TabCptJ[i];
if(CurentLine==(NB_LINE-1) || TabRed[CurentLine]==NB_CASE)
{
AffRes = 1;
EnableWindow(hButton, FALSE);
}
else
{
CurentLine++;
for(i=0; i<NB_CASE; i++) tab[CurentLine][i] = NB_COLOR+1;
}
InvalidateRect(hwnd, NULL, FALSE);
}
return 0;
case WM_SIZE:
MoveWindow(hButton, 0, HIWORD(lParam)-HT_BTN,
LOWORD(lParam), HT_BTN, TRUE);
return 0;
case WM_PAINT :
{
HPEN hp2px;
HGDIOBJ hobjold;
RECT rect;
PAINTSTRUCT ps;
HDC hdc, hdcDB;
HBITMAP bmDB;
int j, k;
hp2px = CreatePen(PS_SOLID, EP_TRAIS, 0x00000000);
GetClientRect(hwnd, &rect);
rect.bottom -= HT_BTN;
hdc = BeginPaint(hwnd, &ps);
hdcDB = CreateCompatibleDC(hdc);
bmDB = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
hobjold = SelectObject(hdcDB, bmDB);
SelectObject(hdcDB,hp2px);
SelectObject(hdcDB,color[0]);
FillRect(hdcDB, &rect, (HBRUSH)(COLOR_WINDOW+1));
for(j=0; j<NB_CASE; j++) // Affichage tableau
for(k=0; k<NB_LINE; k++)
{
SelectObject(hdcDB, color[tab[k][j]]);
Rectangle(hdcDB, POS_TAB_X+j*PAS, POS_TAB_Y-k*PAS,
POS_TAB_X+LARG+j*PAS , POS_TAB_Y+LARG-k*PAS);
}
for(j=0; j<NB_LINE; j++) // Affichage des indices
{
SelectObject(hdcDB, color[1]);
for(k=0; k<TabRed[j]; k++)
Ellipse(hdcDB, POS_IDC_X+k*PAS/2, POS_IDC_Y-j*PAS,
POS_IDC_X+LARG/2+k*PAS/2, POS_IDC_Y-j*PAS+LARG/2);
SelectObject(hdcDB, color[NB_COLOR+1]);
for(k=0; k<TabWhite[j]-TabRed[j]; k++)
Ellipse(hdcDB, POS_IDC_X+k*PAS/2, POS_IDC_Y-j*PAS+LARG/2,
POS_IDC_X+LARG/2+k*PAS/2, POS_IDC_Y-j*PAS+LARG);
}
for(j=0; j<NB_CASE; j++) // Affichage de la solution
{
if(AffRes)SelectObject(hdcDB,color[TabRand[j]]);
else SelectObject(hdcDB,color[0]);
Rectangle(hdcDB, POS_RES_X+j*PAS, POS_RES_Y,
POS_RES_X+LARG+j*PAS , POS_RES_Y+LARG);
}
for(j=0; j<NB_COLOR; j++) // Affichage palette
{
SelectObject(hdcDB,color[j+1]);
Rectangle(hdcDB, POS_PAL_X+j*PAS, POS_PAL_Y,
POS_PAL_X+LARG+j*PAS , POS_PAL_Y+LARG);
}
SelectObject(hdcDB,color[1]);
if(btndown==1) // Drag
{
SelectObject(hdcDB, color[SelColor]);
Rectangle(hdcDB, xPos-LARG/2, yPos-LARG/2, xPos+LARG/2, yPos+LARG/2);
}
BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcDB, 0, 0, SRCCOPY);
SelectObject(hdcDB,hobjold);
DeleteDC(hdcDB);
DeleteObject(bmDB);
EndPaint(hwnd, &ps);
DeleteObject(hp2px);
return 0;
}
case WM_LBUTTONDOWN:
{
int j;
xPos = GET_X_LPARAM(lParam);
yPos = GET_Y_LPARAM(lParam);
j = (xPos-POS_PAL_X)/PAS;
if(yPos>=POS_PAL_Y && yPos<POS_PAL_Y+LARG && xPos>=POS_PAL_X+j*PAS
&& xPos<POS_PAL_X+LARG+j*PAS && j<NB_COLOR && AffRes==0)
{
SelColor = j+1;
btndown = 1;
}
} // pas de return, on execute le code de WM_MOUSEMOVE
case WM_MOUSEMOVE:
if(btndown==1)
{
xPos = GET_X_LPARAM(lParam);
yPos = GET_Y_LPARAM(lParam);
InvalidateRect(hwnd, NULL, FALSE);
}
return 0;
case WM_LBUTTONUP:
if(btndown==1)
{
int j, k;
j = (xPos-POS_TAB_X)/PAS;
k = (POS_TAB_Y+PAS-yPos)/PAS;
if(yPos>=POS_TAB_Y-k*PAS && yPos<POS_TAB_Y+LARG-k*PAS &&
xPos>=POS_TAB_X+j*PAS && xPos<POS_TAB_X+LARG+j*PAS &&
j<NB_CASE && k==CurentLine) tab[k][j] = SelColor;
btndown = 0;
InvalidateRect(hwnd, NULL, FALSE);
}
return 0;
case WM_DESTROY:
{
int i;
for(i=0; i<NB_COLOR+2; i++) DeleteObject(color[i]);
PostQuitMessage(0);
return 0;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

A vos compilateurs.

CGi


Programmation Windows

Création de Fenêtres SDI

Fenêtres
Source code
Dimensions et curseurs
Clavier

Messages

WM_PAINT

Hiword,Lowword

ACCELERATORS

SetWindowLong

Tout ce que l'on devrait savoir

Préambule:

L'objectif içi n'est pas de créer un cas d'école,mais de souligner les principales difficultées et d'obtenir du code réutilisable.La référence est winhelp.Les sources d'informations sont le SDK et les articles en C++ de MSDN.Une fois lu,vous pourrez

Créer une feuille mère et ses filles en une minute

Les différentes techniques de création de fenêtres

Utiliser les Messages Windows

Définitions:

On utilise deux points A et B:

A,point origine(0,0) coin gauche haut de l'écran,(X left,Y top)

      B, (X right,Y bottom)

Windows utilise la structure suivante pour définir la position des fenêtres

              RECT STRUCT

              left dd ?

              top dd ?

              right dd ?

              bottom dd ?

              RECT ENDS

Pour définir la position d'une fenêtre windows , la structure RECT (rectangle) définit les points comme ci dessus

L'origine étant en haut à gauche , pour obtenir la hauteur et la largeur en nombre positif , il faut:

largeur = RECT.right - RECT.left

hauteur=RECT.bottom - RECT.top

Deux fonctions renseignent les valeurs de RECT:

GetWindowRect fournit les coordonnées externes de la fenêtre.Toutes les coordonnées externes démarrent du coin gauche haut de lécran.

GetClientRect fournit les dimensions internes des fenêtres,donnant l'espace disponible pour les contrôles.Seul right et top (coin droit bas) sont différent de zéro (pour un décalage Nul) et donnent donc directement largeur et hauteur disponible.

Pour obtenir ces informations , il faut :

Déclarer une variable à la dimension de RECT:

LOCAL rect:RECT ou rect RECT <>

appeler la fonction:

invoke GetWindowRect,handle,addr rect

Lorsqu'on veut créer ou déplacer des contrôles à l'intérieur d'une fenêtre , les coordonnées deviennent relatives au bord haut gauche de la feuille parente (on n'utilise que le point origine) et deviennent:

    (X,Y),largeur,hauteur aucune structure n'est définie pour les contenir

            ou X=left et Y= top, mesure le décalage du point origine

CreateWindowEx pour créer les fenêtres ou les controles

MoveWindow pour déplacer la fenêtre ou les contrôles

invoke MoveWindow,Handle,X,Y,largeur,hauteur,FALSE

Les valeurs maximums des axes vont être définis par la résolution de l'écran,exemple 800,600
GetSystemMetrics,SystemParametersInfo permettent de retrouver ces dimensions

Utilisation:

Pour positionner les contrôles et les fenêtres , il va falloir jongler avec des nombres a 3 chiffres sans aucune signification et surtout ne pas changer de résolution d'écran.L'utilisation de nombres comme 482,214,10,55 devient vite illisible, si le nombre de contrôles dépasse le nombre des doights d'une main.Au premier essai , on obtient un tas plus ou moins éparpillé de fenêtres et de contrôles.L'utilisation de quelques variables littérales finit d'éclaircir le problème.

La solution consiste à exprimer les coordonnées en pourcentage de résolution de l'écran

Longueur et hauteur de l'écran deviennent égale à 100 , les dimensions à la visualisation restent inchangés quelque soit la résolution de l'écran , les calculs deviennent à 2 chiffres.Il est facile de mémoriser quelques valeurs de dimensions.Un gros bouton fait 9 de haut , une marge 2 ...

Pratique:

La transformation en pourcentage doit s'accomplir avec une certaine précision et l'utilisation du coprocesseur mathématique (FPU Floating Point Unit) s'impose.La fonction GetPercent fournie avec la librairie de masm32 semble résoudre le problème.Hélas , son auteur à oublier que le FPU est sujet au débordement de pile (elle ne peut contenir que 8 nombres flottants st(0) à st(7)) et au bout de 2 ou 3 calculs , ne répond plus que 0.Voici sa version corrigée.

Démarrer l'éxécution du programme fenêtre sous plusieurs résolutions , vous pourrez constater qu'il conserve les mêmes dimensions.Télécharger l'exemple

PROBLEMES d'apparition et de disparition de fenêtres:

Disparition complète:

On peut facilement voire disparaitre complétement une fenêtre de l'écran , en utilisant incorrectement les styles,en voici un exemple:

;==================================================

; rempli la structure WNDCLASSEX

;==================================================

CS_BYTEALIGNWINDOW est le style en trop , et la fenêtre disparait .Rien n'indique dans winhelp la possibilité d'obtenir un tel résultat:

CS_BYTEALIGNWINDOW Aligns a window on a byte boundary (in the x direction) to enhance performance during operations that involve moving or sizing the window. This style affects the width of the window and its horizontal position on the display.

Fenêtre aux contours Flous ,palissant ,s'évanouissant.

Un controle d'édition peut avoir pour parente des tab,ayant pour parente la feuille mére

Lorsque qu'une feuille parente rafraichi son affichage à l'aide de l'évènement PAINT elle rafraichi en même temps ses filles.C'est ce qui occasionne les problèmes d'effacement lorsque la fille utilise son propre évènement paint.La propriété WS_CLIPCHILDREN permet d'empécher la parente de rafraichir en même temps la fille.Encore faut-il appliquer la propriété à toutes les parentes de la feuille fille

Dans le cas qui nous occupe:

La fenêtre principale doit posséder la propriété WS_CLIPCHILDREN
Le contrôle tab doit posséder la propriété WS_CLIPCHILDREN
Le controle d'édition doit posséder la propriété WS_CLIPCHILDREN

Les différentes techniques de création de fenêtres

Windows fournit:

      Les SDI (Simple Document Interface)

      Les MDI(Mutiple Document Interface)

      Les boites de dialogues dans lesquelles il faut distinguer

Les modales (bloquant le déroulement du programme principal)

les modeless (le programme appelant continue ) dans lesquelles il faut distinguer celles dont la forme est contenu dans un fichier de ressource et celles dont la forme est créer en mémoire(masm32 fournit des macros pour les créer)

faire un choix ?:

A chaque type de fenêtre correspond une boucle de programme avec ces propres fonctions de terminaison et ces propres règles d'écriture et pour les boites de dialogue ces propres règles d'accès a la mémoire ,pas toujours explicite.De plus certains styles et styles étendues ne s'appliquent qu'a un certain type de fenêtres.On peut facilement mélanger les styles ensembles et obtenir des fenêtres bloquées,comme dans l'exemple ci-dessus.

La MDI est dédié aux documents possédant plusieurs fenêtres et semble être la forme la plus adaptable a l'évolution du projet,la création de la feuille de départ est différente de la création des feuilles filles

La SDI n'a au départ qu'une seule fenêtre.Elle peut créer autant de filles qu'elle veut en utilisant toujours la même technique . Les feuilles filles possèdent les mêmes propriétés que la feuille mêre.C'est à dire qu'elle peut remplacer la MDI sans problème.Une feuille fille peut servir aussi de boite de dialogue,il suffit d'utiliser CreateWindowEx

Les boites de dialogues dont la forme est défini dans un fichier de ressource présentent l'inconvénient d'avoir une taille fixe en fonction de la résolution d'écran,mais sont simples a écrire et on peut les rendre modifiables en taille.Celles dont la forme est écrite en mémoire sont assez peu utilisées et restes complexes à écrire malgré les macros fournit par masm32 qu'il faut étudier avant d'en créer une.La mémoire réservée dynamiquement par la mère, ne peut pas etre utilisée par la boite

Ma préférence va a la SDI.Je vous livre içi les deux choses à savoir pour qu'elles fonctionnent sans problèmes.
Chaque fille doit avoir son propre nom de classe (et non pas celui de la mère) déclaré avec la fonction RegisterClassEx,le nom de classe est uniquement déterminé par votre imagination.

Les feuilles parentes doivent toutes avoir la propriété WS_CLIPCHILDREN si vous ne voulez pas voir vos feuilles filles s'évanouir(problème de peinture).Pourquoi une telle préférence ?

Avec une instruction (createwindowex) et un type de boucle ,on peut créer tout ce qu'on veut

Messages:

Le proc recevant les messages windows est défini à la création de la fenêtre,dans WNDCLASSEX.(mov wc.lpfnWndProc, offset WndProc) avec

WndProc proto :DWORD,:DWORD,:DWORD,:DWORD

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD

.if uMsg == WM_COMMAND
;éxécuter........

mov eax,0 ;indique que le message a été traité

ret

.elseif uMsg == WM_PAINT

;éxécuter........

jmp SuiteDesMessages

.else

SuiteDesMessages:

invoke DefWindowProc .....

.endif

WndProc endp

la plupart des messages traités dans cette boucle pourront se terminer par un simple ret avec eax null.Il en va tout autrement du message WM_PAINT.Celui ci devra obligatoirement se terminer par la fonction de traitement des messages DefWindowProc(SDI) sous peine de voir des défauts de peinture de la fenêtre.Autrement dit,on ne fait que des rajouts dans ce message.Consulter winhelp pour voir comment utiliser les messages.

La boucle envoi des informations sous forme de dword(32 bits) .La plupart des messages utilisent les mots (16 bits) haut,bas de ce dword.Décomposer systématiquement le dword en deux mots évite bien des erreurs de comparaisons.

Avec editmasm,on évite des recherches et des écritures inutiles,en utilisant le menu Code -->Messages windows.

Créer une fenêtre et des fenêtres filles en une minute ?

L'exemple ci dessous est conçu pour être reproductible et modifiable.Recopier le texte tel que dans le source et changer toutes les occurences de SDI par le nom de votre choix.Placer déclarations et data aux bons endroits.Vous obtiendrez des fenêtres vides a remplir.

Les styles choisies tiennent compte des nécessités de OLE et évitent les problèmes de peinture.Ne les changer pas sans savoir pourquoi.Les dimensions sont en pourcentage de l'écran ou de la feuille mère.Si Hmere=NULL ,on crée une mère,passer le handle de la mère pour créer une fille.Fonction particulière SystemParametersInfo au lieu de getSystemMetric,permet de tenir compte de la barre des taches plus d'autres choses.La boucle d'attente tient compte des recommandations du sdk (en cas de handle invalide) et diffère des autres asm habituelles qui sont toutes ....inprécises.On peut mettre la boucle d'attente directement après le ou les CreerSDI ce qui suprime winmain,parfaitement inutile.Après la boucle exitprocess et c'est tout.

Si au premier essai la fille est invisible,vous avez oublié de modifier le texte de la classe fille

Remplacer SDI par un nom générique , en utilisant le menu d'édition,a partir d'ici

Interroger le clavier

Il existe deux manières d'interroger le clavier:

En créant une ACCELERATORS table (table de touches clavier)

En détournant les messages du control ou de la feuille

En premier on crée une table d'accélérateurs dans la ressource (.rc)

//----------- pour appeler un menu par configuration de touches ------------------

// Accelerators

// ctrl+space shift+insert

SDIDEMO ACCELERATORS DISCARDABLE

BEGIN

VK_SPACE,54, VIRTKEY, CONTROL, NOINVERT

VK_INSERT,55,VIRTKEY, SHIFT, NOINVERT

END

La table est juste une définition des touches que l'on veut employer.Içi, ctrl+espace et shift+inser (voir rc.hlp).Un programme est nécessaire pour utliser ces touches.

Au début du programme

invoke LoadAccelerators,hInstance,SADR("SDIDEMO")

mov Haccel,eax

Enfin il faut renvoyer un message à la fenêtre lorsque les touches sont utilisées

;################################################################

Waiting_Loop PROC

local msg :MSG

Local retour:DWORD

mov retour,1

@@:

INVOKE GetMessage, addr msg,0, 0, 0

.if eax != 0 && eax != INVALID_HANDLE_VALUE ;WM_QUIT ou crash ,quitter sur commande quit

invoke TranslateAccelerator,hWnd,Haccel, ADDR msg;keyboard events en WM_COMMAND

INVOKE TranslateMessage, addr msg ;get the message

INVOKE DispatchMessageA , addr msg ;send les messages aux fenêtres

jmp @B

.endif

quitte:

PuPo retour, msg.wParam

FindeWaiting_Loop:

mov eax,retour

ret

Waiting_Loop endp

Il ne reste plus qu'a intercepter le message:

WM_COMMAND
.elseif eax == 54
;faire quelque choses

Un controle d'édition gère directement les messages clavier envoyés par le système.Une boite de dialogue ne reçoit pas de messages WM_KEYUP .Dans ces deux cas on peut détourner le vecteur d'interruptions pour mettre en place une gestion du clavier.

INVOKE SetWindowLong, Hedit, GWL_WNDPROC, EventEdit
mov lpEdit, eax

Le nouveau proc créer doit repasser la main à la routine d'origine.Il doit aussi mémoriser les touches de combinaison en appui (maj,ctrl,alt...) ou appeler une API pour interroger leur état.

.data
ShiftState DD 0
ControlState DD 0
MenuState DD 0

EventEdit PROC uses esi edi ebx HeditRetour:DWORD, uMsg, wParam, lParam
Local phrase[MAX_PATH]:BYTE,KeyState[256]
ZEROLOCALES KeyState
.if uMsg == WM_KEYDOWN

.if wParam == VK_SHIFT
mov ShiftState,VK_SHIFT
.elseif wParam == VK_LSHIFT
mov ShiftState,VK_LSHIFT
.elseif wParam == VK_RSHIFT
mov ShiftState,VK_RSHIFT
.elseif wParam == VK_CONTROL
mov ControlState,VK_CONTROL
.elseif wParam == VK_RCONTROL
mov ControlState,VK_RCONTROL
.elseif wParam == VK_LCONTROL
mov ControlState,VK_LCONTROL
.elseif wParam == VK_MENU
mov MenuState,VK_MENU
.elseif wParam == VK_LMENU
mov MenuState,VK_LMENU
.elseif wParam == VK_RMENU
mov MenuState,VK_RMENU
.endif

.if wParam == VK_SPACE && ControlState != 0

;envoyer un message à la fenêtre principale ctrl+espace

invoke PostMessage,Hfenetre,WM_COMMAND,54,0

.endif

.if RadioButton == 1
invoke GetKeyNameText,lParam,addr phrase,sizeof phrase
lea eax,phrase
invoke SendDlgItemMessage,HclavBox,IDC_EXPLORATEUREDIT1,WM_SETTEXT,0,addr phrase
.elseif RadioButton == 2
mov eax,lParam
and eax,infoclav.ScanCode
shr eax,16

lea ebx,TabClav
cherche:
.if dword ptr [ebx] != 0 && dword ptr [ebx+4] != 0
mov eax,[ebx]
.if eax == wParam
add ebx,4
invoke lstrcpy,addr phrase,ebx
invoke SendDlgItemMessage,HclavBox,IDC_EXPLORATEUREDIT1,WM_SETTEXT,0,addr phrase
.else
add ebx,4
@@:
.if byte ptr [ebx] != 0
inc ebx
jmp @B
.endif
inc ebx
jmp cherche
.endif
.endif
.elseif RadioButton == 3
comment µ
invoke GetKeyboardState,addr KeyState
invoke ToAscii,wParam,lParam,addr KeyState,addr phrase,MenuState
.if eax == 1 || eax == 2 ;VK_PRIOR
invoke SendDlgItemMessage,HclavBox,IDC_EXPLORATEUREDIT1,WM_SETTEXT,0,addr phrase
.endif
µ
.endif
.if RadioButton != 3
return 0
.endif
.elseif uMsg == WM_KEYUP
.if wParam == VK_SHIFT
mov ShiftState,0
.elseif wParam == VK_LSHIFT
mov ShiftState,0
.elseif wParam == VK_RSHIFT
mov ShiftState,0
.elseif wParam == VK_CONTROL
mov ControlState,0
.elseif wParam == VK_RCONTROL
mov ControlState,0
.elseif wParam == VK_LCONTROL
mov ControlState,0
.elseif wParam == VK_MENU
mov MenuState,0
.elseif wParam == VK_LMENU
mov MenuState,0
.elseif wParam == VK_RMENU
mov MenuState,0
.endif
.if RadioButton != 3
return 0
.endif
.elseif uMsg == WM_CHAR
.if RadioButton != 3
return 0
.endif
.endif

INVOKE CallWindowProc, lpEdit, HeditRetour, uMsg, wParam, lParam
ret
EventEdit ENDP

retour


Tutoriel Win32

Programmation avec l'API Windows

Table des matières Plier Déplier

  • I. Les bases d'un programme Windows
    • I-A. Création d'une boîte de dialogue
      • I-A-1. Cours théorique
      • I-A-2. Projet N°1
    • I-B. Création d'une fenêtre
      • I-B-1. Cours théorique
      • I-B-2. Projet N°2
    • I-C. Affichage dans une fenêtre
      • I-C-1. Cours théorique
      • I-C-2. Projet N°3
    • I-D. Lecture dans un fichier et boîte de dialogue
      • I-D-1. Cours théorique
      • I-D-2. Projet N°4
    • I-E. Pas à pas pour C++ Builder
      • I-E-1. BCB 6
      • I-E-2. BCB 4
    • I-F. Lecture d'un fichier WAV
      • I-F-1. Cours théorique
      • I-F-2. Projet N°5
    • I-G. Création d'une application console
      • I-G-1. Cours théorique
    • I-H. Fenêtre classique ou ressource ?
      • I-H-1. Cours théorique

I. Les bases d'un programme Windows

I-A. Création d'une boîte de dialogue

I-A-1. Cours théorique

Bien comprendre le principe de fonctionnement d'une application Windows est essentiel avant de commencer à programmer. Je vais ici exposer le principe global de fonctionnement d'une application créant une fenêtre.

Tout d'abord, il convient de bien garder à l'esprit qu'un programme fonctionnant sous Windows et gérant une fenêtre doit rester en dialogue permanent avec Windows. Le programme ne connaît (a priori) rien sur son environnement. C'est Windows qui signale à l'application si elle doit redessiner le contenu d'une de ses fenêtres, ou encore si l'utilisateur essaie de fermer la fenêtre.

Cette communication se fait au travers de messages que Windows envoie à chaque fenêtre concernée. C'est au programme d'effectuer la réception de ces messages et de les transmettre aux fonctions gérant les différentes fenêtres. La réception de ces messages ne doit donc pas prendre de retard, et le traitement de chacun des messages doit être bref. En cas de retard, le redessinement des fenêtres n'est donc plus assuré, ce qui a pour conséquence des fenêtres blanches, non déplaçable, similaires à celles des programmes "plantés".

Chaque fenêtre est associée à une fonction ou procédure de fenêtre (Window Proc). Parfois plusieurs fenêtres peuvent être associées à une même procédure. Chaque message reçu est transmis à la procédure correspondante qui se chargera de traiter ce message (redessiner la fenêtre, la redimensionner, afficher un caractère entré par l'utilisateur…).

Une partie du travail de rafraîchissement de la fenêtre est pris en charge par Windows. Le programme n'a à redessiner que la zone client de sa fenêtre (et non pas la barre de titre, les menus éventuels…).

I-A-2. Projet N°1

Objectif : créer une boîte de dialogue et une procédure très simple chargée de la gérer.

Réalisation : la boîte de dialogue sera créée grâce à l'éditeur de ressources de VC++. Le programme sera chargé de la réception des messages et du traitement des plus simples.

Télécharger le projet.

Le projet pas à pas :

Tout d'abord, il s'agit de créer le projet dans VC++ : File/New/Projects/Win32 Application. Donnez ensuite un nom à votre projet et cliquez 'Ok'. Ensuite, sélectionnez 'An empty project' puis 'Finish' et 'Ok'. Nous venons de créer un projet vide, il ne contient aucun fichier de code.

Ajoutons maintenant un fichier de code et fichier de ressources. Allez dans File/New/Files/C++ Source File. Appelez ce fichier main.cpp et validez. Refaites la même opération mais avec un fichier de type Ressource Script nommé res. Fermez le fichier qui s'est ouvert dans l'éditeur. Le script de ressources est associé à un fichier d'en-tête créé et mis à jour automatiquement, mais qu'il convient d'ajouter. Dans le feuillet FileView du panneau de gauche, faites un clic droit sur Header Files et sélectionnez Add Files to Folder. Ajoutez le fichier ressource.h qui doit être dans le même répertoire que votre projet.

Créons à présent le modèle de notre boîte de dialogue. Allez dans le feuillet RessourceView du panneau de travail (à gauche). Faites un clic droit sur res ressources et sélectionnez Insert/Dialog/New. Nous ne modifierons pas cette boîte de dialogue.

Occupons-nous maintenant du code qui va constituer le programme. Tout d'abord, les fichiers d'en-tête, deux sont nécessaires, Windows.h et ressource.h que nous avons inclus précédemment.

Le point d'entrée du programme est la fonction WinMain(). Elle est exécutée automatiquement par Windows lorsque le programme est démarré. Mais avant cette fonction, nous aurons la définition de la procédure de notre fenêtre principale, MainPoc().

#include <Windows.h>
#include "ressource.h"

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{

Nous allons maintenant créer la boîte de dialogue grâce à la fonction CreateDialog(). Le paramètre HINSTANCE est un identifiant de notre application, on passe l'identifiant fourni par Windows en paramètre de la fonction WinMain(). On donne ensuite l'identifiant ressource de notre boîte de dialogue. Elle n'a pas de fenêtre parent et la procédure est la fonction MainProc(). La fonction retourne une variable de type HWND qui identifie notre fenêtre. Puis, on affiche cette fenêtre. Toute fenêtre créée est invisible par défaut.

   HWND hDlg;
   hDlg=CreateDialog(hInstance,(LPCTSTR)IDD_DIALOG1,NULL,(DLGPROC)MainProc);
   ShowWindow(hDlg,SW_SHOW);

Occupons-nous maintenant de la réception des messages. La réception des messages est prise en charge par la fonction GetMessage(). Cette fonction retourne FALSE dès que le message WM_QUIT est reçu, ce qui indique que l'application doit être fermée.

   MSG msg;
   while(GetMessage(&msg,NULL,0,0)==TRUE)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return 0;
}

Il ne reste plus qu'à définir le comportement de notre fenêtre dans quelques cas simples: elle doit être fermée et l'application quittée si l'utilisateur presse 'ok' ou essaie de quitter. La fermeture de la fenêtre ne signifie pas forcément l'arrêt de l'application. Ces deux comportements correspondent à deux messages distincts: WM_QUIT indique que l'application doit se terminer, WM_CLOSE indique la fermeture de la fenêtre. Mais dans ce cas, on a une fenêtre un peu particulière, puisqu'il s'agit d'une boîte de dialogue. Sa procédure est définie de la même manière que celle d'une fenêtre.

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDOK:
         EndDialog(Dlg,0);
         PostQuitMessage(0);
         return TRUE;
      case IDCANCEL:
         EndDialog(Dlg,Select);
         PostQuitMessage(0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

Cette procédure est très simple, elle ne réagit qu'à la pression des boutons 'Ok' et 'Cancel' (le bouton ou la "croix"). En réponse à l'un ou l'autre de ces événements, la procédure demande la fermeture de la boîte de dialogue et termine l'application en envoyant un message WM_QUIT grâce à la fonction PostQuitMessage().

I-B. Création d'une fenêtre

I-B-1. Cours théorique

Dans la première partie de ce chapitre, nous avons vu comment créer une boite de dialogue. La création d'une boite de dialogue grâce à la fonction CreateDialog() est relativement simple puisqu'une grande partie du travail est effectué par CreateDialog().

Nous allons donc voir maintenant comment créer une fenêtre. Cette fenêtre pourra être personnalisée et ne se réferra à aucune ressource. Notre programme n'utilisera donc pas de ressources.

Comme nous avons pu le voir dans la partie précédente, le programme n'intervient en aucun cas dans le dessin de la fenêtre (barre de titre, menu...). Le programme n'est responsable que du redessinement de la zone client de la fenêtre. Comme il existe plusieurs types de fenêtre (avec un menu, sans menu, pouvant être minimisée ou pas...) il est nécessaire que de créer un 'protoype de fenêtre'. Ce prototype est aussi appelé classe de la fenêtre. C'est sur ce prototype que Windows se basera pour créer la fenêtre.

Une fois le prototype de fenêtre créé, nous demanderons à Windows de créer une fenêtre suivant ce prototype. La fenêtre créée devra elle aussi disposer d'une procédure permettant de traiter les messages qu'elle reçoit. Cette procédure est très similaire à celle gérant la boite de dialogue de la partie précédente. Elle ne traite cependant pas exactement les messages de la même manière.

I-B-2. Projet N°2

Objectif : créer une fenêtre sans utiliser de ressource et une procédure permettant sa gestion.

Réalisation : la fenêtre sera créée sans utiliser de ressource et sa procédure traitera les messages les plus simples.

Télécharger le projet.

Le projet pas à pas :

Il s'agit tout d'abord de créer un projet dans Microsoft Visual C++. Le projet sera créé de la même manière que dans la partie précédente, mais sans y ajouter de Ressource Script.

Le point d'entrée WinMain() est un standard à toute application Windows, ce programme débutera donc de la même manière que le précédent.

#include <Windows.h>

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{

Il faut maintenant créer une nouvelle classe de fenêtre. Cette classe est créée au moyen de la fonction RegisterClassEx(). La création de cette classe peut paraître fastidieuse et comprend un grand nombre de paramètres. nous n'utiliserons pas tous les paramètres, donc ne vous effrayez pas si vous ne comprenez pas totalement l'utilité de chacun d'eux.

   WNDCLASSEX principale;
   principale.cbSize=sizeof(WNDCLASSEX);
   principale.style=CS_HREDRAW|CS_VREDRAW;
   principale.lpfnWndProc=MainProc;
   principale.cbClsExtra=0;
   principale.cbWndExtra=0;
   principale.hInstance=hInstance;
   principale.hIcon=LoadIcon(NULL,IDI_APPLICATION);
   principale.hCursor=LoadCursor(NULL,IDC_ARROW);
   principale.hbrBackground=reinterpret_cast<HBRUSH>(COLOR_WINDOW+1);
   principale.lpszMenuName=NULL;
   principale.lpszClassName="std";
   principale.hIconSm=LoadIcon(NULL,IDI_APPLICATION);
   RegisterClassEx(&principale);

principale.style=CS_HREDRAW|CS_VREDRAW indique que notre fenêtre devra être redessinée en cas de redimensionnement horizontal ou vertical. Nous indiquons ensuite quelle procédure sera chargée de gérer la fenêtre. L'instance de notre application est elle aussi passée en paramètre. Les curseurs ou icônes de notre fenêtre sont ceux par défaut de Windows. La fenêtre n'as pas de menu, la couleur de fond est celle de Windows par défaut. Le nom de la classe de la fenêtre 'std' est un choix personnel. Il n'a d'importance que pour nous. Une fois la structure remplie, un appel à RegisterClassEx() demande à Windows de mémoriser la classe.

Maintenant que nous disposons de notre classe de fenêtre, il ne reste plus qu'a la créer grâce à la fonction CreateWindowEx().

   HWND hWnd;
   hWnd=CreateWindowEx(
      WS_EX_CLIENTEDGE,
      "std",
      "Notre fenêtre",
      WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      NULL,
      NULL,
      hInstance,
      NULL
   );

   ShowWindow(hWnd,SW_SHOW);

La réception des messages se fait exactement de la même manière que dans le projet précédent.

   MSG msg;
   while(GetMessage(&msg,NULL,0,0)==TRUE)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return 0;
}

La définition de la procédure de cette fenêtre est la même que dans le programme précédent. Mais son fonctionnement interne est différent.

Tout d'abord, cette fenêtre ne contient pas de bouton. Sa fermeture se fera lors d'un clic sur la 'croix'. Les messages non traités sont passés à DefWindowProc(). Les messages traités seront WM_PAINT et WM_DESTROY. WM_PAINT indique que nous devons redessiner la zone client. Ici le traitement de ce message ne fera rien et pourrait être ignoré. Il est inclus dans le seul but de comprendre le mécanisme de redessinement d'une fenêtre. Le message WM_DESTROY indique que la fenêtre est en train d'être détruite (probablement suite au clic sur la 'croix'). Le fermeture de la fenêtre ne signifie pas forcément l'arrêt de l'application. Mais comme dans ce cas notre application n'est formée que d'une fenêtre, nous demanderons à terminer l'application dans ce cas en postant un message WM_QUIT.

LRESULT CALLBACK MainProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam)
{
   HDC hDC;
   PAINTSTRUCT paintst;
   switch (mes)
   {
   case WM_PAINT:
      hDC=BeginPaint(hWnd,&paintst);
      EndPaint(hWnd,&paintst);
      return 0;
   case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
   default:
      return DefWindowProc(hWnd, mes, wParam, lParam);
   }
}

Le traitement du message WM_PAINT peut vous paraître étrange. En effet, pourquoi effectuer un BeginPaint() et un EndPaint() alors que nous ne dessinons rien. La raison est très simple. À partir du moment ou nous décidons de traiter ce message, nous devons assurer un traitement minimum par la suite de ces deux fonctions, même si rien n'est dessiné.

I-C. Affichage dans une fenêtre

I-C-1. Cours théorique

Maintenant que nous savons créer une fenêtre, nous allons voir comment dessiner dans sa zone client. L'affichage dans la zone client d'une fenêtre se fait au niveau du message WM_PAINT. Pour provoquer l'envoi d'un tel message, il faut demander le redessinement d'une zone de la fenêtre. Lors du dessin dans la partie WM_PAINT, il sera impossible d'afficher hors de la zone de redessinement.

Pour dessiner dans une fenêtre, nous allons utiliser un contexte d'affichage. Il est propre à la fenêtre dans laquelle nous dessinons et nous est fourni par Windows. Il identifie en fait la zone de l'écran dans laquelle nous allons dessiner.

I-C-2. Projet N°3

Objectif : créer une fenêtre et afficher l'heure.

Réalisation : la création de la fenêtre se fera de manière identique au projet précédent. Seule sera ajoutée la partie permettant l'affichage de l'heure courante.

Télécharger le projet.

Le projet pas à pas :

La création de la fenêtre se fait exactement de la même manière que dans le projet précédent. Nous allons simplement utiliser les fonctions contenues dans stdio.h.

#include <Windows.h>
#include <stdio.h>

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
   WNDCLASSEX principale;
   principale.cbSize=sizeof(WNDCLASSEX);
   principale.style=CS_HREDRAW|CS_VREDRAW;
   principale.lpfnWndProc=MainProc;
   principale.cbClsExtra=0;
   principale.cbWndExtra=0;
   principale.hInstance=hInstance;
   principale.hIcon=LoadIcon(NULL,IDI_APPLICATION);
   principale.hCursor=LoadCursor(NULL,IDC_ARROW);
   principale.hbrBackground=reinterpret_cast<HBRUSH>(COLOR_WINDOW+1);
   principale.lpszMenuName=NULL;
   principale.lpszClassName="std";
   principale.hIconSm=LoadIcon(NULL,IDI_APPLICATION);
   RegisterClassEx(&principale);

   HWND hWnd;
   hWnd=CreateWindowEx(
      WS_EX_CLIENTEDGE,
      "std",
      "Notre fenêtre",
      WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      NULL,
      NULL,
      hInstance,
      NULL
   );
   ShowWindow(hWnd,SW_SHOW);

Comme nous allons afficher l'heure, il est important que nous soyons prévenus de manière régulière. Nous allons demander à Windows de nous avertir toutes les secondes, de manière à afficher l'heure régulièrement. Notre fenêtre recevra un message WM_TIMER toutes les secondes.

SetTimer(hWnd,NULL,1000,NULL);

La réception des messages se fait exactement de la même manière que dans le projet précédent.

   MSG msg;
   while(GetMessage(&msg,NULL,0,0)==TRUE)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return 0;
}

La procédure fonctionne de manière identique que précédemment. Nous allons ajouter le traitement du message WM_TIMER qui sera reçu toutes les secondes. Ce message demandera le redessinement d'une zone de l'écran (la zone ou nous afficherons l'heure).

LRESULT CALLBACK MainProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam)
{
   HDC hDC;
   PAINTSTRUCT paintst;
   RECT rcClient;
   switch (mes)
   {
      case WM_TIMER:
         rcClient.top=0;
         rcClient.left=0;
         rcClient.right=100;
         rcClient.bottom=50;
         RedrawWindow(hWnd,&rcClient,NULL,RDW_ERASE|RDW_INVALIDATE|RDW_ERASENOW|RDW_NOCHILDREN);
         return 0;

Un message WM_PAINT sera donc reçu toutes les secondes et à chaque fois que la fenêtre doit être redessinée. La fenêtre doit être redessinée si elle est couverte puis découverte par une autre fenêtre (par exemple).

Nous allons afficher l'heure en utilisant une police spécifique. Une fois cette police créée, il est nécessaire d'indiquer que nous allons l'utiliser dans notre contexte d'affichage (HDC). Le contexte d'affichage (ou Device Context) réfère à la zone client de notre fenêtre et contient tous ses paramètres graphiques (disposition sur l'écran, nombre de couleurs affichables…). Il est identifié par la variable hDC qui nous est passée par Windows au travers de la fonction BeginPaint().

Une fois la police créée et le texte dessiné, il convient de détruire la police.

L'heure courante sera récupérée grâce à la fonction GetLocalTime().

   case WM_PAINT:
      char buf[256];
      SYSTEMTIME CurrentTime;
      HFONT hFont;
      hFont=CreateFont(20,0,0,0,700,FALSE,FALSE,FALSE,0,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH|FF_DONTCARE,"Comic Sans MS");
      hDC=BeginPaint(hWnd,&paintst);
      SelectObject(hDC,hFont);
      GetLocalTime(&CurrentTime);
      sprintf(buf,"%d : %d : %d",CurrentTime.wHour,CurrentTime.wMinute,CurrentTime.wSecond);
      TextOut(hDC,0,0,buf,strlen(buf));
      EndPaint(hWnd,&paintst);
      DeleteObject(hFont);
      return 0;
   case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
   default:
      return DefWindowProc(hWnd, mes, wParam, lParam);
   }
}

Il est essentiel de bien comprendre le principe d'affichage dans une fenêtre car il est très utilisé par Windows. Quel que soit le cas, l'affichage se fait toujours entre deux fonctions qui indiquent le début et la fin de l'affichage. Il ne faut jamais oublier de libérer le contexte d'affichage après l'avoir utilisé. Ici, la libération du contexte d'affichage se fait par la fonction EndPaint().

I-D. Lecture dans un fichier et boîte de dialogue

I-D-1. Cours théorique

Nous allons maintenant revenir sur un programme utilisant des ressources. En effet, l'utilisation de boites de dialogues créées en ressources permettent bien souvent de gagner un temps précieux pour des programmes de petites tailles et de faible complexité. C'est le cas du programme que nous allons réaliser ici.

Nous allons donc aborder deux thèmes principaux: les boites de dialogues et les contrôles prédéfinis, ainsi que la lecture d'un fichier par des fonctions de l'API Windows, donc sans utiliser les fonctions du C traditionnel. Les contrôles prédéfinis sont les 'edit boxes', 'combo boxes', 'buttons', etc. Leur utilisation est très simple, car ils sont gérés de manière entièrement autonome. Ils envoient à la fenêtre qui les a créés des messages de notification pour indiquer les actions de l'utilisateur : clic sur le bouton, saisie d'un caractère…

La lecture du fichier se fait de manière relativement simple, et fonctionne par un système de HANDLE (comme beaucoup de fonctions de l'API Windows) qu'il faut créer puis détruire.

I-D-2. Projet N°4

Objectif : créer une boite de dialogue permettant la saisie d'un fichier et affichant les 500 premiers octets de ce fichier.

Réalisation : nous allons ici utiliser une ressource pour créer la boite de dialogue. La lecture du fichier se fera au niveau de la procédure de la boite de dialogue, car elle est très brève. Nous utiliserons ici une fonction qui permet de créer la boite de dialogue et de relever les messages, la partie 'main' du programme sera donc très courte (2 lignes).

Télécharger le projet.

Le projet pas à pas :

Nous allons créer un programme avec un fichier de ressources, inclure les fichiers correspondants (voir parties précédentes). La fonction WinMain() est excessivement simple puisque la fonction DialogBox() s'occupe de créer la boîte de dialogue et de récupérer ses messages. Cette fonction retourne seulement une fois que la boite de dialogue est fermée.

#include <Windows.h>

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
   DialogBox(hInstance,(LPCTSTR)IDD_MAIN,NULL,(DLGPROC)MainProc);
   return 0;
}

La procédure de la boîte de dialogue est très simple, elle ne gère que trois messages provenant de trois boutons. IDOK est envoyé lors d'un clic sur le bouton OK, IDCANCEL est envoyé lors d'un clic sur la 'croix' et IDC_LIRE est un message envoyé lors d'un clic sur le bouton IDC_LIRE que nous avons créé. Les variables créées en début de procédure serviront à la lecture du fichier que nous verrons après.

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   char buf[501];
   HANDLE hFile;
   DWORD Read;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_LIRE:

La réelle nouveauté de ce programme va se faire ici. Il s'agit tout d'abord de récupérer le nom de fichier entré par l'utilisateur. Comme il est entré dans un contrôle prédéfini, nous allons utiliser la fonction GetDlgItemText() et nous placerons ce nom de fichier dans le buffer créé au début de cette procédure sous le nom de buf. Une fois ce nom de fichier récupéré, nous allons essayer d'ouvrir le fichier donné en lecture. Si nous n'y parvenons pas, un message d'erreur sera affiché grâce à la fonction MessageBox(). Le traitement du message sera alors arrêté.

Remarque: la fonction MessageBox() ne retourne qu'une fois que la boîte de dialogue créée est fermée. Mais la récupération des messages de notre boîte de dialogue est assurée par MessageBox(). Le traitement prolongé de ce message n'est donc pas gênant dans ce cas puisque les messages continueront d'arriver à notre procédure.

         GetDlgItemText(Dlg,IDC_FILENAME,buf,256);
         hFile=CreateFile(buf,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
         if(hFile==INVALID_HANDLE_VALUE)
         {
            MessageBox(Dlg,"Erreur, impossible d'ouvrir le fichier spécifié.","Erreur",MB_OK);
            return 0;
         }

Arrivés à ce stade nous savons que le fichier saisi est valide et qu'il a pu être ouvert. Il ne reste plus qu'à lire les 500 premiers octets. Ils seront placés dans le buffer. Comme cette chaîne vas être affichée, elle doit être terminée par un caractère NULL. C'est pour cette raison que buf a été défini comme un tableau de 501 caractères (500 pour la lecture et 1 pour le caractère NULL). Comme la lecture peut faire moins de 500 octets, nous nous baserons sur la valeur retournée par ReadFile() pour ajouter le caractère NULL. Puis nous afficherons ce buffer dans l'edit box créé a cet effet grâce à SetDlgItemText().

         ReadFile(hFile,buf,500,&Read,NULL);
         CloseHandle(hFile);
         buf[Read]='';
         SetDlgItemText(Dlg,IDC_TEXT,buf);
         return 0;

Le reste du traitement de la procédure ne présente pas de difficulté particulière. Il est similaire aux traitements précédents. La seule différence notable est lors de la fermeture de la boîte de dialogue. Comme elle a été créée avec la fonction DialogBox() il ne faut pas utiliser PostQuitMesage() pour la quitter mais EndDialog().

      case IDOK:
         EndDialog(Dlg,0);
         return TRUE;
      case IDCANCEL:
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

Ce programme est relativement simple. La gestion de l'interface graphique ne prend que quelques lignes. Nous voyons donc bien l'intérêt de l'utilisation des ressources et donc contrôles prédéfinis. Sans l'aide des ressources, ce programme aurait été beaucoup plus compliqué à réaliser.

I-E. Pas à pas pour C++ Builder

Nous allons reprendre la 4ème partie : "Lecture dans un fichier et boîte de dialogue." mais adaptée à C++ Builder. Deux procédures sont traitées dans ce document une pour BCB6 et une pour BCB4.

Pour les projets sans boîte de dialogue la procédure est la même sauf qu'il n'y a pas besoin de rajouter de fichiers ressources.

I-E-1. BCB 6

  • Faire "Nouveau". (appelle la boîte de dialogue Nouveaux éléments)
  • Sur la boîte de dialogue "Expert console" :
  • Sur "Type de source" sélectionner "C" ou "C++". (Pour l'exemple cela n'a pas d'importance)
  • Aucune des CheckBox à droite ne doit être cochée.
  • Cliquer sur le bouton "Ok".
  • Maintenant nous avons le code minimum pour ce type d'application :
#include <windows.h>
#pragma hdrstop

//---------------------------------------------------------------------------
#pragma argsused
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow)
{
        return 0;}
  • Enregister le projet sous le nom choisi. Ici "main.cpp" et "main.bpr". (Faire "Fichier" puis "Tout enregistrer")
  • J'ai utilisé "Resource WorkShop" (livré avec BCB) pour faire le fichier Dialog1.rc mais il peut se faire avec n'importe quel éditeur de ressource.
  • Puis il faut ajouter le fichier ressource au projet:
  • Sur le menu faire "Projet" puis "Ajouter au projet"
  • Selectionner le type de fichier voulu ici "Fichier ressource (*.rc)"
  • Puis sélectionner le fichier ici "Dialog1.rc"
  • Puis faire Ouvrir
  • Le code source est identique a l'exemple Visual C++ (J'ai mis les mêmes noms d'identificateur)
  • Les source des fichiers "Dialog1.rc" et "ressource.h" sont a la fin de ce document.

Fichier "main.cpp" :

#include <windows.h>
#pragma hdrstop //Pas obligatoire
#include "ressource.h"

//---------------------------------------------------------------------------

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

#pragma argsused //ajouté automatiquement mais pas nécéssaire.

WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, in
t nCmdShow)

{
     DialogBox(hInstance,(LPCTSTR)DIALOG_1,NULL,(DLGPROC)MainProc);
     return 0;
}
//---------------------------------------------------------------------------

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   char buf[501];
   HANDLE hFile;
   DWORD Read;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_LIRE:
         GetDlgItemText(Dlg,IDC_FILENAME,buf,256);
         hFile=CreateFile(buf,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
         if(hFile==INVALID_HANDLE_VALUE)
         {
            MessageBox(Dlg,"Erreur, impossible d'ouvrir le fichier spécifié.","Erreur",MB_OK);
            return 0;
         }
         ReadFile(hFile,buf,500,&Read,NULL);
         CloseHandle(hFile);
         buf[Read]='';
         SetDlgItemText(Dlg,IDC_TEXT,buf);
         return 0;

      case IDOK:
         EndDialog(Dlg,0);
         return TRUE;
      case IDCANCEL:
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

I-E-2. BCB 4

  • Faire "Nouveau". (appelle la boîte de dialogue Nouveaux éléments)
  • Sur la boîte de dialogue "Nouveaux éléments" Onglet "Nouveau" DbClk sur l'icône "Expert console". (appelle la boîte de dialogue "Expert d'application console")
  • Sur la boîte de dialogue "Expert d'application console" :
  • Sur "Type de fenêtre" sélectionner "Windows (GUI)".
  • Sur "Type d'exécution" sélectionner "EXE".
  • Clicker sur le bouton "Terminer".
  • Maintenant nous avons le code minimum pour ce type d'application :
#include <windows.h>
#pragma hdrstop
#include <condefs.h>

//---------------------------------------------------------------------------
#pragma argsused
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
        return 0;
}
  • Enregister le projet sous le nom choisi. Ici "main.cpp". (Faire "Fichier" puis "Tout enregistrer")
  • J'ai utilisé "Resource WorkShop" (livré avec BCB) pour faire le fichier Dialog1.rc mais il peut se faire avec n'importe quel éditeur de ressource.
  • Puis il faut ajouter le fichier ressource au projet:
  • Sur le menu faire "Projet" puis "Ajouter au projet"
  • Sélectionner le type de fichier voulu ici "Fichier ressource (*.rc)"
  • Puis sélectionner le fichier ici "Dialog1.rc"
  • Puis faire Ouvrir
  • Cela va ajouter cette ligne dans le code :
USERC("Dialog1.rc");
  • Le reste du code est identique a l'exemple Visual C++ (J'ai mis les mêmes noms d'identificateur)

Fichier "main.cpp" :

#include <windows.h>
#pragma hdrstop
#include <condefs.h>
#include "ressource.h"

//---------------------------------------------------------------------------
USERC("Dialog1.rc");
//---------------------------------------------------------------------------

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM );

#pragma argsused //ajouté automatiquement mais pas nécéssaire.
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
{
   DialogBox(hInstance,(LPCTSTR)DIALOG_1,NULL,(DLGPROC)MainProc);
   return 0;
}

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM )
{
   int Select;
   char buf[501];
   HANDLE hFile;
   DWORD Read;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_LIRE:
         GetDlgItemText(Dlg,IDC_FILENAME,buf,256);
         hFile=CreateFile(buf,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
         if(hFile==INVALID_HANDLE_VALUE)
         {
            MessageBox(Dlg,"Erreur, impossible d'ouvrir le fichier spécifié.","Erreur",MB_OK);
            return 0;
         }
         ReadFile(hFile,buf,500,&Read,NULL);
         CloseHandle(hFile);
         buf[Read]='';
         SetDlgItemText(Dlg,IDC_TEXT,buf);
         return 0;

      case IDOK:
         EndDialog(Dlg,0);
         return TRUE;
     case IDCANCEL:
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}
  • Autres fichiers
  • Identiques pour BCB4 et BCB6

Fichier "Dialog1.rc"

DIALOG_1 DIALOG 14, 30, 212, 227
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "DIALOG_1"
FONT 8, "MS Sans Serif"
{
 CONTROL "Ok", IDOK, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 56, 193, 36, 14
 CONTROL "Cancel", IDCANCEL, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 115, 193, 38, 14
 CONTROL "", IDC_FILENAME, "EDIT", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 26, 18, 91, 12
 CONTROL "", IDC_TEXT, "EDIT", ES_LEFT | ES_MULTILINE | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 18, 49, 175, 119
 CONTROL "Lire", IDC_LIRE, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 136, 18, 50, 14
}

Fichier "Ressource.h"

#define DIALOG_1    1

#define IDC_FILENAME 101
#define IDC_TEXT 102
#define IDC_LIRE 103

I-F. Lecture d'un fichier WAV

I-F-1. Cours théorique

A présent, nous allons réaliser un nouveau projet qui sera déjà un programme 'utile' en soi. Evidemment, il restera très sommaire et se contentera de lire un fichier WAV sélectionné par l'utilisateur.

Nous inviterons tout d'abord l'utilisateur à choisir le fichier qu'il veut lire à partir d'une boîte de dialogue 'parcourir'. Une fois que l'utilisateur aura choisi le fichier qu'il désire lire, il pourra choisir de l'écouter. La lecture du fichier WAV se fera grâce à une fonction multimédia fournie par Windows: sndPlaySound(). Cette fonction est entièrement autonome et lira le fichier en arrière plan sans la moindre intervention de la part du programme. Cet automatisme est très pratique puisqu'il permet de lire le fichier sans se soucier de rien. Cependant, les possibilités de cette fonction restent restreintes. On pourra se satisfaire de cette fonction dans quasiment toutes les applications multimédias.

I-F-2. Projet N°5

Objectif : créer une boite de dialogue permettant la saisie d'un fichier WAV puis sa lecture.

Réalisation : nous allons utiliser un fichier ressources qui sera ici très adapté. Le programme ne contiendra que la fonction WinMain() et une procédure pour la boite de dialogue.

Télécharger le projet.

Le projet pas à pas :

Comme dans le programme précédent, nous allons créer un projet d'application Win32, ainsi qu'un fichier de ressources. La fonction WinMain() contiendra simplement l'appel de notre boîte de dialogue. Remarquons tout de même que la fonction sndPlaySound() que nous allons utiliser se trouve dans la librairie winmm.lib qui n'est pas une librairie standard. Il faut donc ajouter cette librairie. Ceci peut être fait grâce au menu Project/Setting/Link/Input. Dans le champ contenant déjà les librairies par défaut, ajoutez winmm.lib. Sans cet ajout, nous obtiendrions une erreur à la compilation indiquant que la fonction ne peut pas être trouvée.

La boîte de dialogue contiendra un champ 'Edit Box' (IDC_NOMDEFICHIER) dans lequel sera affiché le nom du fichier à lire. Nous cocherons la case 'Read Only' dans les options de ce champ de manière à empêcher l'utilisateur de saisir lui-même un nom de fichier. De cette manière, la possibilité de choix erronés se trouve déjà limitée.

Nous ajouterons un bouton 'Parcourir' avec pour identifiant IDC_PARCOURIR et un bouton 'Lire' (IDC_LIRE). Pour quitter, l'utilisateur devra utiliser la 'croix' (ID_CANCEL).

La fonction WinMain() ne diffère pas par rapport au programme précédent (si ce n'est dans les entêtes à inclure). Nous ajouterons une variable globale de manière à pouvoir accéder à l'instance du programme dans toutes les fonctions du programme (et ici dans la procédure de la boîte de dialogue).

#include <Windows.h>
#include <mmsystem.h>
#include "ressource.h"

HINSTANCE hInst;

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
   hInst=hInstance;
   DialogBox(hInstance,(LPCTSTR)IDD_MAIN,NULL,(DLGPROC)MainProc);
   return 0;
}

La procédure de la boîte de dialogue n'est pas très compliquée. Il faudra toutefois y ajouter les boutons 'Lire' et 'Parcourir'. Le bouton parcourir sera le plus difficile à ajouter.

Nous allons réserver un buffer de taille MAX_PATH (qui est une constante marquant la taille maximale d'un nom de fichier), ainsi qu'une structure OPENFILENAME qui nous servira pour la boîte de dialogue 'Parcourir'.

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   char buf[MAX_PATH];
   OPENFILENAME DlgInfs;

   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_PARCOURIR:

La fonction qui affiche la boîte de dialogue 'Parcourir' est GetOpenfileName(). De manière à personnaliser cette boîte de dialogue à nos besoins, nous passons une structure contenant les caractéristiques de la boîte de dialogue. Comme nous n'utiliserons pas une grande partie des membres de cette structure, nous allons tous les mettre à 0 grâce à la fonction memset() du C standard. Ceci constitue une bonne habitude à prendre. Pour plus d'informations sur les paramètres et les possibilités de cette fonction référez-vous à l'aide de fournie par Microsoft.

         memset(&DlgInfs,0,sizeof(OPENFILENAME));

         DlgInfs.lStructSize=sizeof(OPENFILENAME);
         DlgInfs.hwndOwner=Dlg;
         DlgInfs.hInstance=hInst;
         DlgInfs.lpstrFilter="Fichiers WAV*.WAV";
         DlgInfs.lpstrFile=buf;
         DlgInfs.nMaxFile=MAX_PATH;
         DlgInfs.lpstrTitle="Choisissez le fichier à lire.";
         DlgInfs.Flags=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST;
         if(GetOpenFileName(&DlgInfs))
            SetDlgItemText(Dlg,IDC_NOMDEFICHIER,buf);
         return TRUE;
Lorsque l'utilisateur pressera le bouton 'Lire', il faudra récupérer le nom de fichier saisi dans le champ 'IDC_NOMDEFICHIER' et le lire grâce à la fonction sndPlaySound().
      case IDC_LIRE:
         GetDlgItemText(Dlg,IDC_NOMDEFICHIER,buf,MAX_PATH);

         sndPlaySound(buf,SND_ASYNC);
         return TRUE;
Il ne reste plus qu'à gérer la sortie de la boîte de dialogue. Dans le cas ou l'utilisateur quitte le programme, il faut stopper la lecture du son en cours. Pour cela on utilisera encore la fonction sndPlaySound() avant de quitter.
      case IDCANCEL:
         sndPlaySound(NULL,NULL);
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

Ce programme ne présente aucune difficulté particulière. Il se suffit à lui-même bien qu'étant particulièrement pauvre. Ceci permet de donner une idée de la gestion des boîtes de dialogues qui seront dans le futur intégrées à un programme bien plus important. Bien que la gestion de telles boîtes de dialogue constitue un détail dans un programme plus important, il est nécessaire de bien comprendre leur fonctionnement de manière à ne pas perdre de temps et pouvoir créer rapidement une interface graphique agréable.

I-G. Création d'une application console

I-G-1. Cours théorique

Avant de s'intéresser aux applications consoles, il convient de bien comprendre de quoi il s'agit. Une application console n'est pas un programme DOS. C'est un programme Windows utilisant les fonctions de l'API Windows mais qui s'affiche dans une console DOS au lieu d'une fenêtre classique. Un tel programme ne peut pas être démarré en mode DOS réel.

L'avantage de ces programmes est qu'il est inutile de se préoccuper de l'affichage de la fenêtre puisque toutes les interventions sur la fenêtre sont prises en compte par le système. Ce type d'application est très utile pour de petits utilitaires fonctionnant en ligne de commande (bien qu'ils paraissent austères !).

Cependant, ces programmes fonctionnent de la même manière que les applications DOS (pour ce qui est de l'entrée sortie sur l'écran). On pourra donc utiliser les fonctions printf() et scanf() du C standard pour afficher des données à l'écran. De même les fonctions de flux standard C++ cin et cout sont utilisables.

Cette partie ne fera pas l'objet d'un projet, mais les applications console seront utilisées plus tard pour illustrer des programmes simples et ne nécessitant pas ou peu de communications avec l'utilisateur, de manière à réduire la taille des programmes.

I-H. Fenêtre classique ou ressource ?

I-H-1. Cours théorique

Maintenant que nous avons créé et utilisé ces deux types de fenêtres, il convient de s'interroger sur leur utilisation. Faut-il préférer les ressources ou plutôt les fenêtres classiques ? Quels sont les avantages des une et des autres ?

Les fenêtres créées à partir de ressources (souvent les boîtes de dialogue) sont évidemment bien plus rapides à créer. De plus leur utilisation nécessite beaucoup moins de code. Dans ce cas, pourquoi continuer à utiliser des fenêtres classiques créées avec l'API Windows ?

Si les ressources peuvent séduire, il faut cependant s'en méfier. Elles conviennent très bien pour des boîtes de dialogue invitant l'utilisateur à saisir des valeurs, ou à définir certains paramètres. C'est d'ailleurs leur rôle principal. En les utilisant dans ce contexte on gagne un temps très important par rapport à des fenêtres créées “manuellement”. Cependant, l'affichage dans une boîte de dialogue ne se prête que peu à la personnalisation.

Pour afficher des données comme des images, des polices personnalisées ou autre, il reste préférable d'utiliser une fenêtre classique. De même si la fenêtre doit gérer des interventions utilisateur comme une touche entrée, un clic, il faut alors passer à une fenêtre classique. Dans ces cas, la gestion automatisée devient en fait un obstacle à l'interception de ces événements.

Avant de se lancer dans la programmation d'une fenêtre classique ou au contraire de fenêtres ressources, il faut donc réfléchir à l'utilisation qui sera faite de ces fenêtres. Ni l'une ni l'autre de ces méthodes ne sont à bannir. Au contraire, il faut connaître les deux et savoir utiliser l'une ou l'autre dès qu'il le faut. La facilité des ressources ne doit pas en faire un réflexe systématique.

Bien entendu on ne peut pas établir des caractéristiques types pour lesquelles il faut absolument utiliser tel ou tel type de fenêtre. Cette décision doit prendre en compte les exigences du programme... Je ne me risquerais pas à dresser une liste exhaustive de tous les cas ou l'utilisation de telle ou telle méthode est préférable mais j'invite seulement le programmeur à réfléchir avant d'utiliser les ressources.

/BODY>


Iczelion's Win32asm tutorial part 1: The Basic

Tutorial 1: The Basics

This tutorial assumes that the reader knows how to use MASM. If you're not familiar with MASM, download win32asm.exe and study the text inside the package before going on with the tutorial. Good. You're now ready. Let's go!

Theory:

Win32 programs run in protected mode which is available since 80286. But 80286 is now history. So we only have to concern ourselves with 80386 and its descendants. Windows runs each Win32 program in separated virtual space. That means each Win32 program will have its own 4 GB address space. However, this doesn't mean every win32 program has 4GB of physical memory, only that the program can address any address in that range. Windows will do anything necessary to make the memory the program references valid. Of course, the program must adhere to the rules set by Windows, else it will cause the dreaded General Protection Fault. Each program is alone in its address space. This is in contrast to the situation in Win16. All Win16 programs can *see* each other. Not so under Win32. This feature helps reduce the chance of one program writing over other program's code/data.
Memory model is also drastically different from the old days of the 16-bit world. Under Win32, we need not be concerned with memory model or segments anymore! There's only one memory model: Flat memory model. There's no more 64K segments. The memory is a large continuous space of 4 GB. That also means you don't have to play with segment registers. You can use any segment register to address any point in the memory space. That's a GREAT help to programmers. This is what makes Win32 assembly programming as easy as C.
When you program under Win32, you must know some important rules. One such rule is that, Windows uses esi, edi, ebp and ebx internally and it doesn't expect the values in those registers to change. So remember this rule first: if you use any of those four registers in your callback function, don't ever forget to restore them before returning control to Windows. A callback function is your own function which is called by Windows. The obvious example is the windows procedure. This doesn't mean that you cannot use those four registers, you can. Just be sure to restore them back before passing control back to Windows.

Content:

Here's the skeleton program. If you don't understand some of the codes, don't panic. I'll explain each of them later.

.386
.MODEL Flat, STDCALL
.DATA
<Your initialized data>
......
.DATA?
<Your uninitialized data>
......
.CONST
<Your constants>
......
.CODE
<label>
<Your code>
.....
end <label>

That's all! Let's analyze this skeleton program.

.386
This is an assembler directive, telling the assembler to use 80386 instruction set. You can also use .486, .586 but the safest bet is to stick to .386. There are actually two nearly identical forms for each CPU model. .386/.386p, .486/.486p. Those "p" versions are necessary only when your program uses privileged instructions. Privileged instructions are the instructions reserved by the CPU/operating system when in protected mode. They can only be used by privileged code, such as the virtual device drivers. Most of the time, your program will work in non-privileged mode so it's safe to use non-p versions.

.MODEL FLAT, STDCALL
.MODEL is an assembler directive that specifies memory model of your program. Under Win32, there's only on model, FLAT model.
STDCALL tells MASM about parameter passing convention. Parameter passing convention specifies the order of parameter passing, left-to-right or right-to-left, and also who will balance the stack frame after the function call.
Under Win16, there are two types of calling convention, C and PASCAL
C calling convention passes parameters from right to left, that is , the rightmost parameter is pushed first. The caller is responsible for balancing the stack frame after the call. For example, in order to call a function named foo(int first_param, int second_param, int third_param) in C calling convention the asm codes will look like this:

push [third_param] ; Push the third parameter
push [second_param] ; Followed by the second
push [first_param] ; And the first
call foo
add sp, 12 ; The caller balances the stack frame
PASCAL calling convention is the reverse of C calling convention. It passes parameters from left to right and the callee is responsible for the stack balancing after the call.
Win16 adopts PASCAL convention because it produces smaller codes. C convention is useful when you don't know how many parameters will be passed to the function as in the case of wsprintf(). In the case of wsprintf(), the function has no way to determine beforehand how many parameters will be pushed on the stack, so it cannot do the stack balancing.
STDCALLis the hybrid of C and PASCAL convention. It passes parameter from right to left but the callee is responsible for stack balancing after the call.Win32 platform use STDCALL exclusively. Except in one case: wsprintf(). You must use C calling convention with wsprintf().

.DATA
.DATA?
.CONST
.CODE
All four directives are what's called section. You don't have segments in Win32, remember? But you can divide your entire address space into logical sections. The start of one section denotes the end of the previous section. There'are two groups of section: data and code. Data sections are divided into 3 categories:

  • .DATA This section contains initialized data of your program.
  • .DATA? This section contains uninitialized data of your program. Sometimes you just want to preallocate some memory but don't want to initialize it. This section is for that purpose. The advantage of uninitialized data is: it doesn't take space in the executable file. For example, if you allocate 10,000 bytes in your .DATA? section, your executable is not bloated up 10,000 bytes. Its size stays much the same. You only tell the assembler how much space you need when the program is loaded into memory, that's all.
  • .CONST This section contains declaration of constants used by your program. Constants in this section can never be modified in your program. They are just *constant*.

You don't have to use all three sections in your program. Declare only the section(s) you want to use.

There's only one section for code: .CODE. This is where your codes reside.
<label>
end <label>
where <label> is any arbitrary label is used to specify the extent of your code. Both labels must be identical. All your codes must reside between <label> and end <label>

[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32 ASM tutorial 2: MessageBox

Tutorial 2: MessageBox

In this tutorial, we will create a fully functional Windows program that displays a message box saying "Win32 assembly is great!".

Download the example file here.

Theory:

Windows prepares a wealth of resources for Windows programs. Central to this is the Windows API (Application Programming Interface). Windows API is a huge collection of very useful functions that reside in Windows itself, ready for use by any Windows programs. These functions are stored in several dynamic-linked libraries (DLLs) such as kernel32.dll, user32.dll and gdi32.dll. Kernel32.dll contains API functions that deal with memory and process management. User32.dll controls the user interface aspects of your program. Gdi32.dll is responsible for graphics operations. Other than "the main three", there are other DLLs that your program can use, provided you have enough information about the desired API functions.
Windows programs dynamically link to these DLLs, ie. the codes of API functions are not included in the Windows program executable file. In order for your program to know where to find the desired API functions at runtime, you have to embed that information into the executable file. The information is in import libraries. You must link your programs with the correct import libraries or they will not be able to locate API functions.
When a Windows program is loaded into memory, Windows reads the information stored in the program. That information includes the names of functions the program uses and the DLLs those functions reside in. When Windows finds such info in the program, it'll load the DLLs and perform function address fixups in the program so the calls will transfer control to the right function.
There are two categoriesof API functions: One for ANSI and the other for Unicode. The names of API functions for ANSI are postfixed with "A", eg. MessageBoxA. Those for Unicode are postfixed with "W" (for Wide Char, I think). Windows 95 natively supports ANSI and Windows NT Unicode.
We are usually familiar with ANSI strings, which are arrays of characters terminated by NULL. ANSI character is 1 byte in size. While ANSI code is sufficient for European languages, it cannot handle several oriental languages which have several thousands of unique characters. That's why UNICODE comes in. A UNICODE character is 2 bytes in size, making it possible to have 65536 unique characters in the strings.
But most of the time, you will use an include file which can determine and select the appropriate API functions for your platform. Just refer to API function names without the postfix.

Example:

I'll present the bare program skeleton below. We will flesh it out later.

.386
.model flat, stdcall
.data
.code
start:
end start

The execution starts from the first instruction immediately below the label specified after end directive. In the above skeleton, the execution will start at the first instruction immediately below start label. The execution will proceed instruction by instruction until some flow-control instructions such as jmp, jne, je, ret etc is found. Those instructions redirect the flow of execution to some other instructions. When the program needs to exit to Windows, it should call an API function, ExitProcess.

ExitProcess proto uExitCode:DWORD

The above line is called a function prototype. A function prototype defines the attributes of a function to the assembler/linker so it can do type-checking for you. The format of a function prototype is like this:

FunctionName PROTO [ParameterName]:DataType,[ParameterName]:DataType,...

In short, the name of the function followed by the keyword PROTO and then by the list of data types of the parameters,separated by commas. In the ExitProcess example above, it defines ExitProcess as a function which takes only one parameter of type DWORD. Functions prototypes are very useful when you use the high-level call syntax, invoke. You can think of invoke as a simple call with type-checking. For example, if you do:

call ExitProcess

without pushing a dword onto the stack, the assembler/linker will not be able to catch that error for you. You'll notice it later when your program crashes. But if you use:

invoke ExitProcess

The linker will inform you that you forgot to push a dword on the stack thus avoiding error. I recommend you use invoke instead of simple call. The syntax of invoke is as follows:

INVOKE expression [,arguments]

expression can be the name of a function or it can be a function pointer. The function parameters are separated by commas.

Most of function prototypes for API functions are kept in include files. If you use hutch's MASM32, they will be in MASM32/include folder. The include files have .inc extension and the function prototypes for functions in a DLL is stored in .inc file with the same name as the DLL. For example, ExitProcess is exported by kernel32.lib so the function prototype for ExitProcess is stored in kernel32.inc.
You can also create function prototypes for your own functions.
Throughout my examples, I'll use hutch's windows.inc which you can download from http://win32asm.cjb.net

Now back to ExitProcess, uExitCode parameter is the value you want the program to return to Windows after the program terminates. You can call ExitProcess like this:

invoke ExitProcess, 0

Put that line immediately below start label, you will get a win32 program which immediately exits to Windows, but it's a valid program nonetheless.

.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:
invoke ExitProcess,0
end start

option casemap:none tells MASM to make labels case-sensitive so ExitProcess and exitprocess are different. Note a new directive, include. This directive is followed by the name of a file you want to insert at the place the directive is. In the above example, when MASM processes the line include \masm32\include\windows.inc, it will open windows.inc which is in \MASM32\include folder and process the content of windows.inc as if you paste the content of windows.inc there. hutch's windows.inc contains definitions of constants and structures you need in win32 programming. It doesn't contain any function prototype. windows.inc is by no means comprehensive. hutch and I try to put as many constants and structures into it as possible but there are still many left to be included. It'll be constantly updated. Check out hutch's and my homepage for updates.
From windows.inc, your program got constant and structure definitions. Now for function prototypes, you need to include other include files. They are all stored in \masm32\include folder.

In our example above, we call a function exported by kernel32.dll, so we need to include the function prototypes from kernel32.dll. That file is kernel32.inc. If you open it with a text editor, you will see that it's full of function prototypes for kernel32.dll. If you don't include kernel32.inc, you can still call ExitProcess but only with simple call syntax. You won't be able to invoke the function. The point here is that: in order to invoke a function, you have to put its function prototype somewhere in the source code. In the above example, if you don't include kernel32.inc, you can define the function prototype for ExitProcess anywhere in the source code above the invoke command and it will work. The include files are there to save you the work of typing out the prototypes yourself so use them whenever you can.
Now we encounter a new directive, includelib. includelib doesn't work like include. It 's only a way to tell the assembler what import library your program uses. When the assembler sees an includelib directive, it puts a linker command into the object file so that the linker knows what import libraries your program needs to link with. You're not forced to use includelib though. You can specify the names of the import libraries in the command line of the linker but believe me, it's tedious and the command line can hold only 128 characters.

Now save the example under the name msgbox.asm. Assuming that ml.exe is in your path, assemble msgbox.asm with:

    ml /c /coff /Cp msgbox.asm
  • /c tells MASM to assemble only. Do not invoke link.exe. Most of the time, you would not want to call link.exe automatically since you may have to perform some other tasks prior to calling link.exe.

    • /coff tells MASM to create .obj file in COFF format. MASM uses a variation of COFF (Common Object File Format) which is used under Unix as its own object and executable file format.
    /Cp tells MASM to preserve case of user identifiers. If you use hutch's MASM32 package, you may put "option casemap:none" at the head of your source code, just below .model directive to achieve the same effect.
After you successfully assemble msgbox.asm, you will get msgbox.obj. msgbox.obj is an object file. An object file is only one step away from an executable file. It contains the instructions/data in binary form. What is lacking is some fixups of addresses by the linker.

Then go on with link:

    link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm32\lib msgbox.obj
/SUBSYSTEM:WINDOWS informs Link what sort of executable this program is
/LIBPATH:<path to import library> tells Link where the import libraries are. If you use MASM32, they will be in MASM32\lib folder.
Link reads in the object file and fixes it with addresses from the import libraries. When the process is finished you get msgbox.exe.

Now you get msgbox.exe. Go on, run it. You'll find that it does nothing. Well, we haven't put anything interesting into it yet. But it's a Windows program nonetheless. And look at its size! In my PC, it is 1,536 bytes.

Next we're going to put in a message box. Its function prototype is:

MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD

hwnd is the handle to parent window. You can think of a handle as a number that represents the window you're referrring to. Its value is not important to you. You only remember that it represents the window. When you want to do anything with the window, you must refer to it by its handle.
lpText is a pointer to the text you want to display in the client area of the message box. A pointer is really an address of something. A pointer to text string==The address of that string.
lpCaption is a pointer to the caption of the message box
uType specifies the icon and the number and type of buttons on the message box
Let's modify msgbox.asm to include the message box.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib

.data
MsgBoxCaption db "Iczelion Tutorial No.2",0
MsgBoxText db "Win32 Assembly is Great!",0

.code
start:
invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start

Assemble and run it. You will see a message box displaying the text "Win32 Assembly is Great!".

Let's look again at the source code.
We define two zero-terminated strings in .data section. Remember that every ANSI string in Windows must be terminated by NULL (0 hexadecimal).
We use two constants, NULL and MB_OK. Those constants are documented in windows.inc. So you can refer to them by name instead of the values. This improves readability of your source code.
The addr operator is used to pass the address of a label to the function. It's valid only in the context of invoke directive. You can't use it to assign the address of a label to a register/variable, for example. You can use offset instead of addr in the above example. However, there are some differences between the two:

  1. addr cannot handle forward reference while offset can. For example, if the label is defined somewhere further in the source code than the invoke line, addr will not work.
    2. invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
    ......
    MsgBoxCaption db "Iczelion Tutorial No.2",0
    MsgBoxText db "Win32 Assembly is Great!",0
    MASM will report error. If you use offset instead of addr in the above code snippet, MASM will assemble it happily.
  2. addr can handle local variables while offset cannot. A local variable is only some reserved space in the stack. You will only know its address during runtime. offset is interpreted during assembly time by the assembler. So it's natural that offset won't work for local variables. addr is able to handle local variables because of the fact that the assembler checks first whether the variable referred to by addr is a global or local one. If it's a global variable, it puts the address of that variable into the object file. In this regard, it works like offset. If it's a local variable, it generates an instruction sequence like this before it actually calls the function:
    3. lea eax, LocalVar
    push eax

    Since lea can determine the address of a label at runtime, this works fine.

[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32 ASM tutorial 3: A Simple Window

Tutorial 3: A Simple Window

In this tutorial, we will build a Windows program that displays a fully functional window on the desktop.
Download the example file here

Theory:

Windows programs rely heavily on API functions for their GUI. This approach benefits both users and programmers. For users, they don't have to learn how to navigate the GUI of each new programs, the GUI of Windows programs are alike. For programmers, the GUI codes are already there,tested, and ready for use. The downside for programmers is the increased complexity involved. In order to create or manipulate any GUI objects such as windows, menu or icons, programmers must follow a strict recipe. But that can be overcome by modular programming or OOP paradigm.
I'll outline the steps required to create a window on the desktop below:
  1. Get the instance handle of your program (required)
  2. Get the command line (not required unless your program wants to process a command line)
  3. Register window class (required ,unless you use predefined window types, eg. MessageBox or a dialog box)
  4. Create the window (required)
  5. Show the window on the desktop (required unless you don't want to show the window immediately)
  6. Refresh the client area of the window
  7. Enter an infinite loop, checking for messages from Windows
  8. If messages arrive, they are processed by a specialized function that is responsible for the window
  9. Quit program if the user closes the window
As you can see, the structure of a Windows program is rather complex compared to a DOS program. But the world of Windows is drastically different from the world of DOS. Windows programs must be able to coexist peacefully with each other. They must follow stricter rules. You, as a programmer, must also be more strict with your programming style and habit.

Content:

Below is the source code of our simple window program. Before jumping into the gory details of Win32 ASM programming, I'll point out some fine points which will ease your programming.
  • You should put all Windows constants, structures and function prototypes in an include file and include it at the beginning of your .asm file. It'll save you a lot of effort and typo. Currently, the most complete include file for MASM is hutch's windows.inc which you can download from his page or my page. You can also define your own constants & structure definitions but you should put them into a separate include file.
  • Use includelib directive to specify the import library used in your program. For example, if your program calls MessageBox, you should put the line:
      • includelib user32.lib
    at the beginning of your .asm file. This directive tells MASM that your program will make uses of functions in that import library. If your program calls functions in more than one library, just add an includelib for each library you use. Using IncludeLib directive, you don't have to worry about import libraries at link time. You can use /LIBPATH linker switch to tell Link where all the libs are.
  • When declaring API function prototypes, structures, or constants in your include file, try to stick to the original names used in Windows include files, including case. This will save you a lot of headache when looking up some item in Win32 API reference.
  • Use makefile to automate your assembling process. This will save you a lot of typing.
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib ; calls to functions in user32.lib and kernel32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA ; initialized data
ClassName db "SimpleWinClass",0 ; the name of our window class
AppName db "Our First Window",0 ; the name of our window

.DATA? ; Uninitialized data
hInstance HINSTANCE ? ; Instance handle of our program
CommandLine LPSTR ?
.CODE ; Here begins our code
start:
invoke GetModuleHandle, NULL ; get the instance handle of our program.
; Under Win32, hmodule==hinstance mov hInstance,eax
mov hInstance,eax
invoke GetCommandLine ; get the command line. You don't have to call this function IF
; your program doesn't process the command line.
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT ; call the main function
invoke ExitProcess, eax ; quit our program. The exit code is returned in eax from WinMain.

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX ; create local variables on stack
LOCAL msg:MSG
LOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEX ; fill values in members of wc
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc ; register our window class
invoke CreateWindowEx,NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow ; display our window on desktop
invoke UpdateWindow, hwnd ; refresh the client area

.WHILE TRUE ; Enter message loop
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam ; return exit code in eax
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY ; if the user closes our window
invoke PostQuitMessage,NULL ; quit our application
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; Default message processing
ret
.ENDIF
xor eax,eax
ret
WndProc endp

end start

Analysis:

You may be taken aback that a simple Windows program requires so much coding. But most of those codes are just *template* codes that you can copy from one source code file to another. Or if you prefer, you could assemble some of these codes into a library to be used as prologue and epilogue codes. You can write only the codes in WinMain function. In fact, this is what C compilers do. They let you write WinMain codes without worrying about other housekeeping chores. The only catch is that you must have a function named WinMain else C compilers will not be able to combine your codes with the prologue and epilogue. You do not have such restriction with assembly language. You can use any function name instead of WinMain or no function at all.
Prepare yourself. This's going to be a long, long tutorial. Let's analyze this program to death!
    .386
    .model flat,stdcall
    option casemap:none

    WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

    include \masm32\include\windows.inc
    include \masm32\include\user32.inc
    include \masm32\include\kernel32.inc
    includelib \masm32\lib\user32.lib
    includelib \masm32\lib\kernel32.lib

The first three lines are "necessities". .386 tells MASM we intend to use 80386 instruction set in this program. .model flat,stdcall tells MASM that our program uses flat memory addressing model. Also we will use stdcall parameter passing convention as the default one in our program.
Next is the function prototype for WinMain. Since we will call WinMain later, we must define its function prototype first so that we will be able to invoke it.
We must include windows.inc at the beginning of the source code. It contains important structures and constants that are used by our program. The include file , windows.inc, is just a text file. You can open it with any text editor. Please note that windows.inc does not contain all structures, and constants (yet). hutch and I are working on it. You can add in new items if they are not in the file.
Our program calls API functions that reside in user32.dll (CreateWindowEx, RegisterWindowClassEx, for example) and kernel32.dll (ExitProcess), so we must link our program to those two import libraries. The next question : how can I know which import library should be linked to my program? The answer: You must know where the API functions called by your program reside. For example, if you call an API function in gdi32.dll, you must link with gdi32.lib.
This is the approach of MASM. TASM 's way of import library linking is much more simpler: just link to one and only one file: import32.lib.
    .DATA
    ClassName db "SimpleWinClass",0
    AppName db "Our First Window",0

    .DATA?
    hInstance HINSTANCE ?
    CommandLine LPSTR ?

Next are the "DATA" sections.
In .DATA, we declare two zero-terminated strings(ASCIIZ strings): ClassName which is the name of our window class and AppName which is the name of our window. Note that the two variables are initialized.
In .DATA?, two variables are declared: hInstance (instance handle of our program) and CommandLine (command line of our program). The unfamiliar data types, HINSTANCE and LPSTR, are really new names for DWORD. You can look them up in windows.inc. Note that all variables in .DATA? section are not initialized, that is, they don't have to hold any specific value on startup, but we want to reserve the space for future use.
    .CODE
    start:
    invoke GetModuleHandle, NULL
    mov hInstance,eax
    invoke GetCommandLine
    mov CommandLine,eax
    invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
    invoke ExitProcess,eax
    .....
    end start
.CODE contains all your instructions. Your codes must reside between <starting label>: and end <starting label>. The name of the label is unimportant. You can name it anything you like so long as it is unique and doesn't violate the naming convention of MASM.
Our first instruction is the call to GetModuleHandle to retrieve the instance handle of our program. Under Win32, instance handle and module handle are one and the same. You can think of instance handle as the ID of your program. It is used as parameter to several API functions our program must call, so it's generally a good idea to retrieve it at the beginning of our program.
Note: Actually under win32, instance handle is the linear address of your program in memory.
Upon returning from a Win32 function, the function's return value, if any, can be found in eax. All other values are returned through variables passed in the function parameter list you defined for the call.
A Win32 function that you call will nearly always preserve the segment registers and the ebx, edi, esi and ebp registers. Conversely, ecx and edx are considered scratch registers and are always undefined upon return from a Win32 function.
Note: Don't expect the values of eax, ecx, edx to be preserved across API function calls.
The bottom line is that: when calling an API function, expects return value in eax. If any of your function will be called by Windows, you must also play by the rule: preserve and restore the values of the segment registers, ebx, edi, esi and ebp upon function return else your program will crash very shortly, this includes your window procedure and windows callback functions.
The GetCommandLine call is unnecessary if your program doesn't process a command line. In this example, I show you how to call it in case you need it in your program.
Next is the WinMain call. Here it receives four parameters: the instance handle of our program, the instance handle of the previous instance of our program, the command line and window state at first appearance. Under Win32, there's NO previous instance. Each program is alone in its address space, so the value of hPrevInst is always 0. This is a leftover from the day of Win16 when all instances of a program run in the same address space and an instance wants to know if it's the first instance. Under win16, if hPrevInst is NULL, then this instance is the first one.
Note: You don't have to declare the function name as WinMain. In fact, you have complete freedom in this regard. You don't have to use any WinMain-equivalent function at all. You can paste the codes inside WinMain function next to GetCommandLine and your program will still be able to function perfectly.
Upon returning from WinMain, eax is filled with exit code. We pass that exit code as the parameter to ExitProcess which terminates our application.

WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

The above line is the function declaration of WinMain. Note the parameter:type pairs that follow PROC directive. They are parameters that WinMain receives from the caller. You can refer to these parameters by name instead of by stack manipulation. In addition, MASM will generate the prologue and epilogue codes for the function. So we don't have to concern ourselves with stack frame on function enter and exit.

LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

LOCAL directive allocates memory from the stack for local variables used in the function. The bunch of LOCAL directives must be immediately below the PROC directive. The LOCAL directive is immediately followed by <the name of local variable>:<variable type>. So LOCAL wc:WNDCLASSEX tells MASM to allocate memory from the stack the size of WNDCLASSEX structure for the variable named wc. We can refer to wc in our codes without any difficulty involved in stack manipulation. That's really a godsend, I think. The downside is that local variables cannot be used outside the function they're created and will be automatically destroyed when the function returns to the caller. Another drawback is that you cannot initialize local variables automatically because they're just stack memory allocated dynamically when the function is entered . You have to manually assign them with desired values after LOCAL directives.

mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc

The inimidating lines above are really simple in concept. It just takes several lines of instruction to accomplish. The concept behind all these lines is window class. A window class is nothing more than a blueprint or specification of a window. It defines several important characteristics of a window such as its icon, its cursor, the function responsible for it, its color etc. You create a window from a window class. This is some sort of object oriented concept. If you want to create more than one window with the same characteristics, it stands to reason to store all these characteristics in only one place and refer to them when needed. This scheme will save lots of memory by avoiding duplication of information. Remember, Windows is designed in the past when memory chips are prohibitive and most computers have 1 MB of memory. Windows must be very efficient in using the scarce memory resource. The point is: if you define your own window, you must fill the desired characteristics of your window in a WNDCLASS or WNDCLASSEX structure and call RegisterClass or RegisterClassEx before you're able to create your window. You only have to register the window class once for each window type you want to create a window from.
Windows has several predefined Window classes, such as button and edit box. For these windows (or controls), you don't have to register a window class, just call CreateWindowEx with the predefined class name.
The single most important member in the WNDCLASSEX is lpfnWndProc. lpfn stands for long pointer to function. Under Win32, there's no "near" or "far" pointer, just pointer because of the new FLAT memory model. But this is again a leftover from the day of Win16. Each window class must be associated with a function called window procedure. The window procedure is responsible for message handling of all windows created from the associated window class. Windows will send messages to the window procedure to notify it of important events concerning the windows it 's responsible for,such as user keyboard or mouse input. It's up to the window procedure to respond intelligently to each window message it receives. You will spend most of your time writing event handlers in window procedure.
I describe each member of WNDCLASSEX below:

WNDCLASSEX STRUCT DWORD
cbSize DWORD ?
style DWORD ?
lpfnWndProc DWORD ?
cbClsExtra DWORD ?
cbWndExtra DWORD ?
hInstance DWORD ?
hIcon DWORD ?
hCursor DWORD ?
hbrBackground DWORD ?
lpszMenuName DWORD ?
lpszClassName DWORD ?
hIconSm DWORD ?
WNDCLASSEX ENDS

cbSize: The size of WNDCLASSEX structure in bytes. We can use SIZEOF operator to get the value.
style: The style of windows created from this class. You can combine several styles together using "or" operator.
lpfnWndProc: The address of the window procedure responsible for windows created from this class.
cbClsExtra: Specifies the number of extra bytes to allocate following the window-class structure. The operating system initializes the bytes to zero. You can store window class-specific data here.
cbWndExtra: Specifies the number of extra bytes to allocate following the window instance. The operating system initializes the bytes to zero. If an application uses the WNDCLASS structure to register a dialog box created by using the CLASS directive in the resource file, it must set this member to DLGWINDOWEXTRA.
hInstance: Instance handle of the module.
hIcon: Handle to the icon. Get it from LoadIcon call.
hCursor: Handle to the cursor. Get it from LoadCursor call.
hbrBackground: Background color of windows created from the class.
lpszMenuName: Default menu handle for windows created from the class.
lpszClassName: The name of this window class.
hIconSm: Handle to a small icon that is associated with the window class. If this member is NULL, the system searches the icon resource specified by the hIcon member for an icon of the appropriate size to use as the small icon.

invoke CreateWindowEx, NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL

After registering the window class, we can call CreateWindowEx to create our window based on the submitted window class. Notice that there are 12 parameters to this function.

CreateWindowExA proto dwExStyle:DWORD,\
lpClassName:DWORD,\
lpWindowName:DWORD,\
dwStyle:DWORD,\
X:DWORD,\
Y:DWORD,\
nWidth:DWORD,\
nHeight:DWORD,\
hWndParent:DWORD ,\
hMenu:DWORD,\
hInstance:DWORD,\
lpParam:DWORD

Let's see detailed description of each parameter:
dwExStyle: Extra window styles. This is the new parameter that is added to the old CreateWindow. You can put new window styles for Windows 95 & NT here.You can specify your ordinary window style in dwStyle but if you want some special styles such as topmost window, you must specify them here. You can use NULL if you don't want extra window styles.
lpClassName: (Required). Address of the ASCIIZ string containing the name of window class you want to use as template for this window. The Class can be your own registered class or predefined window class. As stated above, every window you created must be based on a window class.
lpWindowName: Address of the ASCIIZ string containing the name of the window. It'll be shown on the title bar of the window. If this parameter is NULL, the title bar of the window will be blank.
dwStyle: Styles of the window. You can specify the appearance of the window here. Passing NULL is ok but the window will have no system menu box, no minimize-maximize buttons, and no close-window button. The window would not be of much use at all. You will need to press Alt+F4 to close it. The most common window style is WS_OVERLAPPEDWINDOW. A window style is only a bit flag. Thus you can combine several window styles by "or" operator to achieve the desired appearance of the window. WS_OVERLAPPEDWINDOW style is actually a combination of the most common window styles by this method.
X,Y: The coordinate of the upper left corner of the window. Normally this values should be CW_USEDEFAULT, that is, you want Windows to decide for you where to put the window on the desktop.
nWidth, nHeight: The width and height of the window in pixels. You can also use CW_USEDEFAULT to let Windows choose the appropriate width and height for you.
hWndParent: A handle to the window's parent window (if exists). This parameter tells Windows whether this window is a child (subordinate) of some other window and, if it is, which window is the parent. Note that this is not the parent-child relationship of multiple document interface (MDI). Child windows are not bound to the client area of the parent window. This relationship is specifically for Windows internal use. If the parent window is destroyed, all child windows will be destroyed automatically. It's really that simple. Since in our example, there's only one window, we specify this parameter as NULL.
hMenu: A handle to the window's menu. NULL if the class menu is to be used. Look back at the a member of WNDCLASSEX structure, lpszMenuName. lpszMenuName specifies *default* menu for the window class. Every window created from this window class will have the same menu by default. Unless you specify an *overriding* menu for a specific window via its hMenu parameter. hMenu is actually a dual-purpose parameter. In case the window you want to create is of a predefined window type (ie. control), such control cannot own a menu. hMenu is used as that control's ID instead. Windows can decide whether hMenu is really a menu handle or a control ID by looking at lpClassName parameter. If it's the name of a predefined window class, hMenu is a control ID. If it's not, then it's a handle to the window's menu.
hInstance: The instance handle for the program module creating the window.
lpParam: Optional pointer to a data structure passed to the window. This is used by MDI window to pass the CLIENTCREATESTRUCT data. Normally, this value is set to NULL, meaning that no data is passed via CreateWindow(). The window can retrieve the value of this parameter by the call to GetWindowLong function.

mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow
invoke UpdateWindow, hwnd

On successful return from CreateWindowEx, the window handle is returned in eax. We must keep this value for future use. The window we just created is not automatically displayed. You must call ShowWindow with the window handle and the desired *display state* of the window to make it display on the screen. Next you can call UpdateWindow to order your window to repaint its client area. This function is useful when you want to update the content of the client area. You can omit this call though.

.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW

At this time, our window is up on the screen. But it cannot receive input from the world. So we have to *inform* it of relevant events. We accomplish this with a message loop. There's only one message loop for each module. This message loop continually checks for messages from Windows with GetMessage call. GetMessage passes a pointer to a MSG structure to Windows. This MSG structure will be filled with information about the message that Windows want to send to a window in the module. GetMessage function will not return until there's a message for a window in the module. During that time, Windows can give control to other programs. This is what forms the cooperative multitasking scheme of Win16 platform. GetMessage returns FALSE if WM_QUIT message is received which, in the message loop, will terminate the loop and exit the program.
TranslateMessage is a utility function that takes raw keyboard input and generates a new message (WM_CHAR) that is placed on the message queue. The message with WM_CHAR contains the ASCII value for the key pressed, which is easier to deal with than the raw keyboard scan codes. You can omit this call if your program doesn't process keystrokes.
DispatchMessage sends the message data to the window procedure responsible for the specific window the message is for.

mov eax,msg.wParam
ret
WinMain endp

If the message loop terminates, the exit code is stored in wParam member of the MSG structure. You can store this exit code into eax to return it to Windows. At the present time, Windows does not make use of the return value, but it's better to be on the safe side and plays by the rule.

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

This is our window procedure. You don't have to name it WndProc. The first parameter, hWnd, is the window handle of the window that the message is destined for. uMsg is the message. Note that uMsg is not a MSG structure. It's just a number, really. Windows defines hundreds of messages, most of which your programs will not be interested in. Windows will send an appropriate message to a window in case something relevant to that window happens. The window procedure receives the message and reacts to it intelligently. wParam and lParam are just extra parameters for use by some messages. Some messages do send accompanying data in addition to the message itself. Those data are passed to the window procedure by means of lParam and wParam.

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

Here comes the crucial part. This is where most of your program's intelligence resides. The codes that respond to each Windows message are in the window procedure. Your code must check the Windows message to see if it's a message it's interested in. If it is, do anything you want to do in response to that message and then return with zero in eax. If it's not, you MUST call DefWindowProc, passing all parameters you received to it for default processing.. This DefWindowProc is an API function that processes the messages your program are not interested in.
The only message that you MUST respond to is WM_DESTROY. This message is sent to your window procedure whenever your window is closed. By the time your window procedure receives this message, your window is already removed from the screen. This is just a notification that your window was destroyed, you should prepare yourself to return to Windows. In response to this, you can perform housekeeping prior to returning to Windows. You have no choice but to quit when it comes to this state. If you want to have a chance to stop the user from closing your window, you should process WM_CLOSE message. Now back to WM_DESTROY, after performing housekeeping chores, you must call PostQuitMessage which will post WM_QUIT back to your module. WM_QUIT will make GetMessage return with zero value in eax, which in turn, terminates the message loop and quits to Windows. You can send WM_DESTROY message to your own window procedure by calling DestroyWindow function.

[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32 Assembly Tutorial 4: Painting with Text

Tutorial 4: Painting with Text

In this tutorial, we will learn how to "paint" text in the client area of a window. We'll also learn about device context.
You can download the source code here.

Theory:

Text in Windows is a type of GUI object. Each character is composed of numerous pixels (dots) that are lumped together into a distinct pattern. That's why it's called "painting" instead of "writing". Normally, you paint text in your own client area (actually, you can paint outside client area but that's another story). Putting text on screen in Windows is drastically different from DOS. In DOS, you can think of the screen in 80x25 dimension. But in Windows, the screen are shared by several programs. Some rules must be enforced to avoid programs writing over each other's screen. Windows ensures this by limiting painting area of each window to its own client area only. The size of client area of a window is also not constant. The user can change the size anytime. So you must determine the dimensions of your own client area dynamically.
Before you can paint something on the client area, you must ask for permission from Windows. That's right, you don't have absolute control of the screen as you were in DOS anymore. You must ask Windows for permission to paint your own client area. Windows will determine the size of your client area, font, colors and other GDI attributes and sends a handle to device context back to your program. You can then use the device context as a passport to painting on your client area.
What is a device context? It's just a data structure maintained internally by Windows. A device context is associated with a particular device, such as a printer or video display. For a video display, a device context is usually associated with a particular window on the display.
Some of the values in the device context are graphic attributes such as colors, font etc. These are default values which you can change at will. They exist to help reduce the load from having to specify these attributes in every GDI function calls.
You can think of a device context as a default environment prepared for you by Windows. You can override some default settings later if you so wish.
When a program need to paint, it must obtain a handle to a device context. Normally, there are several ways to accomplish this.
    call BeginPaint in response to WM_PAINT message.
    call GetDC in response to other messages.
    call CreateDC to create your own device context
One thing you must remember, after you're through with the device context handle, you must release it during the processing of a single message. Don't obtain the handle in response to one message and release it in response to another.
Windows posts WM_PAINT messages to a window to notify that it's now time to repaint its client area. Windows does not save the content of client area of a window. Instead, when a situation occurs that warrants a repaint of client area (such as when a window was covered by another and is just uncovered), Windows puts WM_PAINT message in that window's message queue. It's the responsibility of that window to repaint its own client area. You must gather all information about how to repaint your client area in the WM_PAINT section of your window procedure, so the window procudure can repaint the client area when WM_PAINT message arrives.
Another concept you must come to terms with is the invalid rectangle. Windows defines an invalid rectangle as the smallest rectangular area in the client area that needs to be repainted. When Windows detects an invalid rectangle in the client area of a window , it posts WM_PAINT message to that window. In response to WM_PAINT message, the window can obtain a paintstruct structure which contains, among others, the coordinate of the invalid rectangle. You call BeginPaint in response to WM_PAINT message to validate the invalid rectangle. If you don't process WM_PAINT message, at the very least you must call DefWindowProc or ValidateRect to validate the invalid rectangle else Windows will repeatedly send you WM_PAINT message.
Below are the steps you should perform in response to a WM_PAINT message:
    Get a handle to device context with BeginPaint.
    Paint the client area.
    Release the handle to device context with EndPaint
Note that you don't have to explicitly validate the invalid rectangle. It's automatically done by the BeginPaint call. Between BeginPaint-Endpaint pair, you can call any GDI functions to paint your client area. Nearly all of them require the handle to device context as a parameter.

Content:

We will write a program that displays a text string "Win32 assembly is great and easy!" in the center of the client area.
.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
OurText db "Win32 assembly is great and easy!",0

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.CODE
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke GetClientRect,hWnd, ADDR rect
invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax, eax
ret
WndProc endp
end start

Analysis:

The majority of the code is the same as the example in tutorial 3. I'll explain only the important changes.

LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT

These are local variables that are used by GDI functions in our WM_PAINT section. hdc is used to store the handle to device context returned from BeginPaint call. ps is a PAINTSTRUCT structure. Normally you don't use the values in ps. It's passed to BeginPaint function and Windows fills it with appropriate values. You then pass ps to EndPaint function when you finish painting the client area. rect is a RECT structure defined as follows:

RECT Struct
left LONG ?
top LONG ?
right LONG ?
bottom LONG ?
RECT ends
Left and top are the coordinates of the upper left corner of a rectangle Right and bottom are the coordinates of the lower right corner. One thing to remember: The origin of the x-y axes is at the upper left corner of the client area. So the point y=10 is BELOW the point y=0.

invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke GetClientRect,hWnd, ADDR rect
invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,hWnd, ADDR ps

In response to WM_PAINT message, you call BeginPaint with handle to the window you want to paint and an uninitialized PAINTSTRUCT structure as parameters. After successful call, eax contains the handle to device context. Next you call GetClientRect to retrieve the dimension of the client area. The dimension is returned in rect variable which you pass to DrawText as one of its parameters. DrawText's syntax is:

DrawText proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD

DrawText is a high-level text output API function. It handles some gory details such as word wrap, centering etc. so you could concentrate on the string you want to paint. Its low-level brother, TextOut, will be examined in the next tutorial. DrawText formats a text string to fit within the bounds of a rectangle. It uses the currently selected font,color and background (in the device context) to draw the text.Lines are wrapped to fit within the bounds of the rectangle. It returns the height of the output text in device units, in our case, pixels. Let's see its parameters:

    hdc handle to device context
    lpString The pointer to the string you want to draw in the rectangle. The string must be null-terminated else you would have to specify its length in the next parameter, nCount.
    nCount The number of characters to output. If the string is null-terminated, nCount must be -1. Otherwise nCount must contain the number of characters in the string you want to draw.
    lpRect The pointer to the rectangle (a structure of type RECT) you want to draw the string in. Note that this rectangle is also a clipping rectangle, that is, you could not draw the string outside this rectangle.
    uFormat The value that specifies how the string is displayed in the rectangle. We use three values combined by "or" operator:
    • DT_SINGLELINE specifies a single line of text
    • DT_CENTER centers the text horizontally.
    • DT_VCENTER centers the text vertically. Must be used with DT_SINGLELINE.
After you finish painting the client area, you must call EndPaint function to release the handle to device context.
That's it. We can summarize the salient points here:
  • You call BeginPaint-EndPaint pair in response to WM_PAINT message.
  • Do anything you like with the client area between the calls to BeginPaint and EndPaint.
  • If you want to repaint your client area in response to other messages, you have two choices:
    • Use GetDC-ReleaseDC pair and do your painting between these calls
    • Call InvalidateRect or UpdateWindow to invalidate the entire client area, forcing Windows to put WM_PAINT message in the message queue of your window and do your painting in WM_PAINT section
[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32 Assembly Tutorial 5: More about Text

Tutorial 5: More about Text

We will experiment more with text attributes, ie. font and color.

Download the example file here.

Theory:

Windows color system is based on RGB values, R=red, G=Green, B=Blue. If you want to specify a color in Windows, you must state your desired color in terms of these three major colors. Each color value has a range from 0 to 255 (a byte value). For example, if you want pure red color, you should use 255,0,0. Or if you want pure white color, you must use 255,255,255. You can see from the examples that getting the color you need is very difficult with this system since you have to have a good grasp of how to mix and match colors.
For text color and background, you use SetTextColor and SetBkColor, both of them require a handle to device context and a 32-bit RGB value. The 32-bit RGB value's structure is defined as:

RGB_value struct
unused db 0
blue db ?
green db ?
red db ?
RGB_value ends

Note that the first byte is not used and should be zero. The order of the remaining three bytes is reversed,ie. blue, green, red. However, we will not use this structure since it's cumbersome to initialize and use. We will create a macro instead. The macro will receive three parameters: red, green and blue values. It'll produce the desired 32-bit RGB value and store it in eax. The macro is as follows:

RGB macro red,green,blue
xor eax,eax
mov ah,blue
shl eax,8
mov ah,green
mov al,red
endm

You can put this macro in the include file for future use.
You can "create" a font by calling CreateFont or CreateFontIndirect. The difference between the two functions is that CreateFontIndirect receives only one parameter: a pointer to a logical font structure, LOGFONT. CreateFontIndirect is the more flexible of the two especially if your programs need to change fonts frequently. However, in our example, we will "create" only one font for demonstration, we can get away with CreateFont. After the call to CreateFont, it will return a handle to a font which you must select into the device context. After that, every text API function will use the font we have selected into the device context.

Content:

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

RGB macro red,green,blue
xor eax,eax
mov ah,blue
shl eax,8
mov ah,green
mov al,red
endm

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
TestString db "Win32 assembly is great and easy!",0
FontName db "script",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL hfont:HFONT

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\
ADDR FontName
invoke SelectObject, hdc, eax
mov hfont,eax
RGB 200,200,50
invoke SetTextColor,hdc,eax
RGB 0,0,255
invoke SetBkColor,hdc,eax
invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString
invoke SelectObject,hdc, hfont
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

end start

Analysis:

invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\
ADDR FontName

CreateFont creates a logical font that is the closest match to the given parameters and the font data available. This function has more parameters than any other function in Windows. It returns a handle to logical font to be used by SelectObject function. We will examine its parameters in detail.

CreateFont proto nHeight:DWORD,\
nWidth:DWORD,\
nEscapement:DWORD,\
nOrientation:DWORD,\
nWeight:DWORD,\
cItalic:DWORD,\
cUnderline:DWORD,\
cStrikeOut:DWORD,\
cCharSet:DWORD,\
cOutputPrecision:DWORD,\
cClipPrecision:DWORD,\
cQuality:DWORD,\
cPitchAndFamily:DWORD,\
lpFacename:DWORD

nHeight The desired height of the characters . 0 means use default size.
nWidth The desired width of the characters. Normally this value should be 0 which allows Windows to match the width to the height. However, in our example, the default width makes the characters hard to read, so I use the width of 16 instead.
nEscapement Specifies the orientation of the next character output relative to the previous one in tenths of a degree. Normally, set to 0. Set to 900 to have all the characters go upward from the first character, 1800 to write backwards, or 2700 to write each character from the top down.
nOrientation Specifies how much the character should be rotated when output in tenths of a degree. Set to 900 to have all the characters lying on their backs, 1800 for upside-down writing, etc.
nWeight Sets the line thickness of each character. Windows defines the following sizes:

    FW_DONTCARE equ 0
    FW_THIN equ 100
    FW_EXTRALIGHT equ 200
    FW_ULTRALIGHT equ 200
    FW_LIGHT equ 300
    FW_NORMAL equ 400
    FW_REGULAR equ 400
    FW_MEDIUM equ 500
    FW_SEMIBOLD equ 600
    FW_DEMIBOLD equ 600
    FW_BOLD equ 700
    FW_EXTRABOLD equ 800
    FW_ULTRABOLD equ 800
    FW_HEAVY equ 900
    FW_BLACK equ 900
cItalic 0 for normal, any other value for italic characters.
cUnderline 0 for normal, any other value for underlined characters.
cStrikeOut 0 for normal, any other value for characters with a line through the center.
cCharSet The character set of the font. Normally should be OEM_CHARSET which allows Windows to select font which is operating system-dependent.
cOutputPrecision Specifies how much the selected font must be closely matched to the characteristics we want. Normally should be OUT_DEFAULT_PRECIS which defines default font mapping behavior.
cClipPrecision Specifies the clipping precision. The clipping precision defines how to clip characters that are partially outside the clipping region. You should be able to get by with CLIP_DEFAULT_PRECIS which defines the default clipping behavior.
cQuality Specifies the output quality. The output quality defines how carefully GDI must attempt to match the logical-font attributes to those of an actual physical font. There are three choices: DEFAULT_QUALITY, PROOF_QUALITY and DRAFT_QUALITY.
cPitchAndFamily Specifies pitch and family of the font. You must combine the pitch value and the family value with "or" operator.
lpFacename A pointer to a null-terminated string that specifies the typeface of the font.

The description above is by no means comprehensive. You should refer to your Win32 API reference for more details.

invoke SelectObject, hdc, eax
mov hfont,eax

After we get the handle to the logical font, we must use it to select the font into the device context by calling SelectObject. SelectObject puts the new GDI objects such as pens, brushs, and fonts into the device context to be used by GDI functions. It returns the handle to the replaced object which we should save for future SelectObject call. After SelectObject call, any text output function will use the font we just selected into the device context.

RGB 200,200,50
invoke SetTextColor,hdc,eax
RGB 0,0,255
invoke SetBkColor,hdc,eax

Use RGB macro to create a 32-bit RGB value to be used by SetColorText and SetBkColor.

invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString

Call TextOut function to draw the text on the client area. The text will be in the font and color we specified previously.

invoke SelectObject,hdc, hfont

When we are through with the font, we should restore the old font back into the device context. You should always restore the object that you replaced in the device context.

[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32 Assembly Tutorial 6: Keyboard Input

Tutorial 6: Keyboard Input

We will learn how a Windows program receives keyboard input.

Download the example here.

Theory:

Since normally there's only one keyboard in each PC, all running Windows programs must share it between them. Windows is responsible for sending the key strokes to the window which has the input focus.
Although there may be several windows on the screen, only one of them has the input focus. The window which has input focus is the only one which can receive key strokes. You can differentiate the window which has input focus from other windows by looking at the title bar. The title bar of the window which has input focus is highlighted.
Actually, there are two main types of keyboard messages, depending on your view of the keyboard. You can view a keyboard as a collection of keys. In this case, if you press a key, Windows sends a WM_KEYDOWN message to the window which has input focus, notifying that a key is pressed. When you release the key, Windows sends a WM_KEYUP message. You treat a key as a button. Another way to look at the keyboard is that it's a character input device. When you press "a" key, Windows sends a WM_CHAR message to the window which has input focus, telling it that the user sends "a" character to it. In fact, Windows sends WM_KEYDOWN and WM_KEYUP messages to the window which has input focus and those messages will be translated to WM_CHAR messages by TranslateMessage call. The window procedure may decide to process all three messages or only the messages it's interested in. Most of the time, you can ignore WM_KEYDOWN and WM_KEYUP since TranslateMessage function call in the message loop translate WM_KEYDOWN and WM_KEYUP messages to WM_CHAR messages. We will focus on WM_CHAR in this tutorial.

Example:

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
char WPARAM 20h ; the character the program receives from keyboard

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke TextOut,hdc,0,0,ADDR char,1
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analysis:

char WPARAM 20h ; the character the program receives from keyboard

This is the variable that will store the character received from the keyboard. Since the character is sent in WPARAM of the window procedure, we define the variable as type WPARAM for simplicity. The initial value is 20h or the space since when our window refreshes its client area the first time, there is no character input. So we want to display space instead.

.ELSEIF uMsg==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE

This is added in the window procedure to handle the WM_CHAR message. It just puts the character into the variable named "char" and then calls InvalidateRect. InvalidateRect makes the specified rectangle in the client area invalid which forces Windows to send WM_PAINT message to the window procedure. Its syntax is as follows:

InvalidateRect proto hWnd:HWND,\
lpRect:DWORD,\
bErase:DWORD

lpRect is a pointer to the rectagle in the client area that we want to declare invalid. If this parameter is null, the entire client area will be marked as invalid.
bErase is a flag telling Windows if it needs to erase the background. If this flag is TRUE, then Windows will erase the backgroud of the invalid rectangle when BeginPaint is called.

So the strategy we used here is that: we store all necessary information relating to painting the client area and generate WM_PAINT message to paint the client area. Of course, the codes in WM_PAINT section must know beforehand what's expected of them. This seems a roundabout way of doing things but it's the way of Windows.
Actually we can paint the client area during processing WM_CHAR message by calling GetDC and ReleaseDC pair. There is no problem there. But the fun begins when our window needs to repaint its client area. Since the codes that paint the character are in WM_CHAR section, the window procedure will not be able to repaint our character in the client area. So the bottom line is: put all necessary data and codes that do painting in WM_PAINT. You can send WM_PAINT message from anywhere in your code anytime you want to repaint the client area.

invoke TextOut,hdc,0,0,ADDR char,1

When InvalidateRect is called, it sends a WM_PAINT message back to the window procedure. So the codes in WM_PAINT section is called. It calls BeginPaint as usual to get the handle to device context and then call TextOut which draws our character in the client area at x=0, y=0. When you run the program and press any key, you will see that character echo in the upper left corner of the client window. And when the window is minimized and maximized again, the character is still there since all the codes and data essential to repaint are all gathered in WM_PAINT section.

[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32 Assembly Tutorial 7: Mouse Input

Tutorial 7: Mouse Input

We will learn how to receive and respond to mouse input in our window procedure. The example program will wait for left mouse clicks and display a text string at the exact clicked spot in the client area.

Download the example here.

Theory:

As with keyboard input, Windows detects and sends notifications about mouse activities that are relevant to each window. Those activities include left and right clicks, mouse cursor movement over window, double clicks. Unlike keyboard input which is directed to the window that has input focus, mouse messages are sent to any window that the mouse cursor is over, active or not. In addition, there are mouse messages about the non-client area too. But most of the time, we can blissfully ignore them. We can focus on those relating to the client area.
There are two messages for each mouse button: WM_LBUTTONDOWN,WM_RBUTTONDOWN and WM_LBUTTONUP, WM_RBUTTONUP messages. For a mouse with three buttons, there are also WM_MBUTTONDOWN and WM_MBUTTONUP. When the mouse cursor moves over the client area, Windows sends WM_MOUSEMOVE messages to the window under the cursor.
A window can receive double click messages, WM_LBUTTONDBCLK or WM_RBUTTONDBCLK, if and only if its window class has CS_DBLCLKS style flag, else the window will receive only a series of mouse button up and down messages.
For all these messages, the value of lParam contains the position of the mouse. The low word is the x-coordinate, and the high word is the y-coordinate relative to upper left corner of the client area of the window. wParam indicates the state of the mouse buttons and Shift and Ctrl keys.

Example:

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MouseClick db 0 ; 0=no click yet

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hitpoint POINT <>

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_LBUTTONDOWN
mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax
mov eax,lParam
shr eax,16
mov hitpoint.y,eax
mov MouseClick,TRUE
invoke InvalidateRect,hWnd,NULL,TRUE
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
.IF MouseClick
invoke lstrlen,ADDR AppName
invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax
.ENDIF
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analysis:

.ELSEIF uMsg==WM_LBUTTONDOWN
mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax
mov eax,lParam
shr eax,16
mov hitpoint.y,eax
mov MouseClick,TRUE
invoke InvalidateRect,hWnd,NULL,TRUE

The window procedure waits for left mouse button click. When it receives WM_LBUTTONDOWN, lParam contains the coordinate of the mouse cursor in the client area. It saves the coordinate in a variable of type POINT which is defined as:

POINT STRUCT
x dd ?
y dd ?
POINT ENDS

and sets the flag, MouseClick, to TRUE, meaning that there's at least a left mouse button click in the client area.

mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax

Since x-coordinate is the low word of lParam and the members of POINT structure are 32-bit in size, we have to zero out the high word of eax prior to storing it in hitpoint.x.

shr eax,16
mov hitpoint.y,eax

Because y-coordinate is the high word of lParam, we must put it in the low word of eax prior to storing it in hitpoint.y. We do this by shifting eax 16 bits to the right.
After storing the mouse position, we set the flag, MouseClick, to TRUE in order to let the painting code in WM_PAINT section know that there's at least a click in the client area so it can draw the string at the mouse position. Next we call InvalidateRect function to force the window to repaint its entire client area.

.IF MouseClick
invoke lstrlen,ADDR AppName
invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax
.ENDIF

The painting code in WM_PAINT section must check if MouseClick is true, since when the window was created, it received a WM_PAINT message which at that time, no mouse click had occurred so it should not draw the string in the client area. We initialize MouseClick to FALSE and change its value to TRUE when an actual mouse click occurs.
If at least one mouse click has occurred, it draws the string in the client area at the mouse position. Note that it calls lstrlen to get the length of the string to display and sends the length as the last parameter of TextOut function.

[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32 Assembly Tutorial 8: Menu

Tutorial 8: Menu

In this tutorial, we will learn how to incorporate a menu into our window.
Download the example 1 and example 2.

Theory:

Menu is one of the most important component in your window. Menu presents a list of services a program offers to the user. The user doesn't have to read the manual included with the program to be able to use it, he can peruse the menu to get an overview of the capability of a particular program and start playing with it immediately. Since a menu is a tool to get the user up and running quickly, you should follow the standard. Succintly put, the first two menu items should be File and Edit and the last should be Help. You can insert your own menu items between Edit and Help. If a menu item invokes a dialog box, you should append an ellipsis (...) to the menu string.
Menu is a kind of resource. There are several kinds of resources such as dialog box, string table, icon, bitmap, menu etc. Resources are described in a separated file called a resource file which normally has .rc extension. You then combine the resources with the source code during the link stage. The final product is an executable file which contains both instructions and resources.
You can write resource scripts using any text editor. They're composed of phrases which describe the appearances and other attributes of the resources used in a particular program Although you can write resource scripts with a text editor, it's rather cumbersome. A better alternative is to use a resource editor which lets you visually design resources with ease. Resource editors are usually included in compiler packages such as Visual C++, Borland C++, etc.
You describe a menu resource like this:
    MyMenu MENU
    {
    [menu list here]
    }
C programmers may recognize that it is similar to declaring a structure. MyMenu being a menu name followed by MENU keyword and menu list within curly brackets. Alternatively, you can use BEGIN and END instead of the curly brackets if you wish. This syntax is more palatable to Pascal programmers.
Menu list can be either MENUITEM or POPUP statement.
MENUITEM statement defines a menu bar which doesn't invoke a popup menu when selected.The syntax is as follows:
    MENUITEM "&text", ID [,options]
It begins by MENUITEM keyword followed by the text you want to use as menu bar string. Note the ampersand. It causes the character that follows it to be underlined. Following the text string is the ID of the menu item. The ID is a number that will be used to identify the menu item in the message sent to the window procedure when the menu item is selected. As such, each menu ID must be unique among themselves.
Options are optional. Available options are as follows:
  • GRAYED The menu item is inactive, and it does not generate a WM_COMMAND message. The text is grayed.
  • INACTIVE The menu item is inactive, and it does not generate a WM_COMMAND message. The text is displayed normally.
  • MENUBREAK This item and the following items appear on a new line of the menu.
  • HELP This item and the following items are right-justified.
You can use one of the above option or combine them with "or" operator. Beware that INACTIVE and GRAYED cannot be combined together.
POPUP statement has the following syntax:
POPUP "&text" [,options]
{
[menu list]
}
POPUP statement defines a menu bar that, when selected, drops down a list of menu items in a small popup window. The menu list can be a MENUTIEM or POPUP statement. There's a special kind of MENUITEM statement, MENUITEM SEPARATOR, which will draw a horizontal line in the popup window.
The next step after you are finished with the menu resource script is to reference it in your program.
You can do this in two different places in your program.
  • In lpszMenuName member of WNDCLASSEX structure. Say, if you have a menu named "FirstMenu", you can assigned the menu to your window like this:
        • .DATA
          MenuName db "FirstMenu",0
        ...........................
        ...........................
        .CODE
          ...........................
          mov wc.lpszMenuName, OFFSET MenuName
          ...........................
  • In menu handle parameter of CreateWindowEx like this:
        • .DATA
          MenuName db "FirstMenu",0
          hMenu HMENU ?
        ...........................
        ...........................
        .CODE
          ...........................
          invoke LoadMenu, hInst, OFFSET MenuName
          mov hMenu, eax
          invoke CreateWindowEx,NULL,OFFSET ClsName,\
          OFFSET Caption, WS_OVERLAPPEDWINDOW,\
          CW_USEDEFAULT,CW_USEDEFAULT,\
          CW_USEDEFAULT,CW_USEDEFAULT,\
          NULL,\
          hMenu,\
          hInst,\
          NULL\
          ...........................
So you may ask, what's the difference between these two methods?
When you reference the menu in the WNDCLASSEX structure, the menu becomes the "default" menu for the window class. Every window of that class will have the same menu.
If you want each window created from the same class to have different menus, you must choose the second form. In this case, any window that is passed a menu handle in its CreateWindowEx function will have a menu that "overrides" the default menu defined in the WNDCLASSEX structure.
Next we will examine how a menu notifies the window procedure when the user selects a menu item.
When the user selects a menu item, the window procedure will receive a WM_COMMAND message. The low word of wParam contains the menu ID of the selected menu item.
Now we have sufficient information to create and use a menu. Let's do it.

Example:

The first example shows how to create and use a menu by specifying the menu name in the window class.

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0 ; The name of our menu in the resource file.
Test_string db "You selected Test menu item",0
Hello_string db "Hello, my friend",0
Goodbye_string db "See you again, bye",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.const
IDM_TEST equ 1 ; Menu IDs
IDM_HELLO equ 2
IDM_GOODBYE equ 3
IDM_EXIT equ 4

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName ; Put our menu name here
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF ax==IDM_TEST
invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK
.ELSEIF ax==IDM_HELLO
invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK
.ELSEIF ax==IDM_GOODBYE
invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
**************************************************************************************************************************

Menu.rc
**************************************************************************************************************************

#define IDM_TEST 1
#define IDM_HELLO 2
#define IDM_GOODBYE 3
#define IDM_EXIT 4

FirstMenu MENU
{
POPUP "&PopUp"
{
MENUITEM "&Say Hello",IDM_HELLO
MENUITEM "Say &GoodBye", IDM_GOODBYE
MENUITEM SEPARATOR
MENUITEM "E&xit",IDM_EXIT
}
MENUITEM "&Test", IDM_TEST
}

Analysis:

Let's analyze the resource file first.
#define IDM_TEST 1 /* equal to IDM_TEST equ 1*/
#define IDM_HELLO 2
#define IDM_GOODBYE 3
#define IDM_EXIT 4
The above lines define the menu IDs used by the menu script. You can assign any value to the ID as long as the value is unique in the menu.

FirstMenu MENU

Declare your menu with MENU keyword.

POPUP "&PopUp"
{
MENUITEM "&Say Hello",IDM_HELLO
MENUITEM "Say &GoodBye", IDM_GOODBYE
MENUITEM SEPARATOR
MENUITEM "E&xit",IDM_EXIT
}

Define a popup menu with four menu items, the third one is a menu separator.

MENUITEM "&Test", IDM_TEST

Define a menu bar in the main menu.
Next we will examine the source code.

MenuName db "FirstMenu",0 ; The name of our menu in the resource file.
Test_string db "You selected Test menu item",0
Hello_string db "Hello, my friend",0
Goodbye_string db "See you again, bye",0
MenuName is the name of the menu in the resource file. Note that you can define more than one menu in the resource file so you must specify which menu you want to use. The remaining three lines define the text strings to be displayed in message boxes that are invoked when the appropriate menu item is selected by the user.
IDM_TEST equ 1 ; Menu IDs
IDM_HELLO equ 2
IDM_GOODBYE equ 3
IDM_EXIT equ 4
Define menu IDs for use in the window procedure. These values MUST be identical to those defined in the resource file.

.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF ax==IDM_TEST
invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK
.ELSEIF ax==IDM_HELLO
invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK
.ELSEIF ax==IDM_GOODBYE
invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF

In the window procedure, we process WM_COMMAND messages. When the user selects a menu item, the menu ID of that menu item is sended to the window procedure in the low word of wParam along with the WM_COMMAND message. So when we store the value of wParam in eax, we compare the value in ax to the menu IDs we defined previously and act accordingly. In the first three cases, when the user selects Test, Say Hello, and Say GoodBye menu items, we just display a text string in a message box.
If the user selects Exit menu item, we call DestroyWindow with the handle of our window as its parameter which will close our window.
As you can see, specifying menu name in a window class is quite easy and straightforward. However you can also use an alternate method to load a menu in your window. I won't show the entire source code here. The resource file is the same in both methods. There are some minor changes in the source file which I 'll show below.

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ? ; handle of our menu
Define a variable of type HMENU to store our menu handle.

invoke LoadMenu, hInst, OFFSET MenuName
mov hMenu,eax
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,hMenu,\
hInst,NULL

Before calling CreateWindowEx, we call LoadMenu with the instance handle and a pointer to the name of our menu. LoadMenu returns the handle of our menu in the resource file which we pass to CreateWindowEx.

[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32 Assembly Tutorial 9: Child Window Controls

Tutorial 9: Child Window Controls

In this tutorial, we will explore child window controls which are very important input and output devices of our programs.

Download the example here.

Theory:

Windows provides several predefined window classes which we can readily use in our own programs. Most of the time we use them as components of a dialog box so they're usually called child window controls. The child window controls process their own mouse and keyboard messages and notify the parent window when their states have changed. They relieve the burden from programmers enormously so you should use them as much as possible. In this tutorial, I put them on a normal window just to demonstrate how you can create and use them but in reality you should put them in a dialog box.
Examples of predefined window classes are button, listbox, checkbox, radio button,edit etc.
In order to use a child window control, you must create it with CreateWindow or CreateWindowEx. Note that you don't have to register the window class since it's registered for you by Windows. The class name parameter MUST be the predefined class name. Say, if you want to create a button, you must specify "button" as the class name in CreateWindowEx. The other parameters you must fill in are the parent window handle and the control ID. The control ID must be unique among the controls. The control ID is the ID of that control. You use it to differentiate between the controls.
After the control was created, it will send messages notifying the parent window when its state has changed. Normally, you create the child windows during WM_CREATE message of the parent window. The child window sends WM_COMMAND messages to the parent window with its control ID in the low word of wParam, the notification code in the high word of wParam, and its window handle in lParam. Each child window control has different notification codes, refer to your Win32 API reference for more information.
The parent window can send commands to the child windows too, by calling SendMessage function. SendMessage function sends the specified message with accompanying values in wParam and lParam to the window specified by the window handle. It's an extremely useful function since it can send messages to any window provided you know its window handle.
So, after creating the child windows, the parent window must process WM_COMMAND messages to be able to receive notification codes from the child windows.

Example:

We will create a window which contains an edit control and a pushbutton. When you click the button, a message box will appear showing the text you typed in the edit box. There is also a menu with 4 menu items:
  1. Say Hello -- Put a text string into the edit box
  2. Clear Edit Box -- Clear the content of the edit box
  3. Get Text -- Display a message box with the text in the edit box
  4. Exit -- Close the program.
.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0
ButtonClassName db "button",0
ButtonText db "My First Button",0
EditClassName db "edit",0
TestString db "Wow! I'm in an edit box now",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndButton HWND ?
hwndEdit HWND ?
buffer db 512 dup(?) ; buffer to store the text retrieved from the edit box

.const
ButtonID equ 1 ; The control ID of the button control
EditID equ 2 ; The control ID of the edit control
IDM_HELLO equ 1
IDM_CLEAR equ 2
IDM_GETTEXT equ 3
IDM_EXIT equ 4

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName, \
ADDR AppName, WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT, CW_USEDEFAULT,\
300,200,NULL,NULL, hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\
ES_AUTOHSCROLL,\
50,35,200,25,hWnd,8,hInstance,NULL
mov hwndEdit,eax
invoke SetFocus, hwndEdit
invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR ButtonText,\
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
75,70,140,25,hWnd,ButtonID,hInstance,NULL
mov hwndButton,eax
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analysis:

Let's analyze the program.
    .ELSEIF uMsg==WM_CREATE
    invoke CreateWindowEx,WS_EX_CLIENTEDGE, \
    ADDR EditClassName,NULL,\
    WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT\
    or ES_AUTOHSCROLL,\
    50,35,200,25,hWnd,EditID,hInstance,NULL
    mov hwndEdit,eax
    invoke SetFocus, hwndEdit
    invoke CreateWindowEx,NULL, ADDR ButtonClassName,\
    ADDR ButtonText,\
    WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
    75,70,140,25,hWnd,ButtonID,hInstance,NULL
    mov hwndButton,eax
We create the controls during processing of WM_CREATE message. We call CreateWindowEx with an extra window style, WS_EX_CLIENTEDGE, which makes the client area look sunken. The name of each control is a predefined one, "edit" for edit control, "button" for button control. Next we specify the child window's styles. Each control has extra styles in addition to the normal window styles. For example, the button styles are prefixed with "BS_" for "button style", edit styles are prefixed with "ES_" for "edit style". You have to look these styles up in a Win32 API reference. Note that you put a control ID in place of the menu handle. This doesn't cause any harm since a child window control cannot have a menu.
After creating each control, we keep its handle in a variable for future use.
SetFocus is called to give input focus to the edit box so the user can type the text into it immediately.
Now comes the really exciting part. Every child window control sends notification to its parent window with WM_COMMAND.

.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0

Recall that a menu also sends WM_COMMAND messages to notify the window about its state too. How can you differentiate between WM_COMMAND messages originated from a menu or a control? Below is the answer

 | Low word of wParam | High word of wParam | lParam
Menu | Menu ID | 0 | 0
Control | Control ID | Notification code | Child Window Handle

You can see that you should check lParam. If it's zero, the current WM_COMMAND message is from a menu. You cannot use wParam to differentiate between a menu and a control since the menu ID and control ID may be identical and the notification code may be zero.

.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK

You can put a text string into an edit box by calling SetWindowText. You clear the content of an edit box by calling SetWindowText with NULL. SetWindowText is a general purpose API function. You can use SetWindowText to change the caption of a window or the text on a button.
To get the text in an edit box, you use GetWindowText.

.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF

The above code snippet deals with the condition when the user presses the button. First, it checks the low word of wParam to see if the control ID matches that of the button. If it is, it checks the high word of wParam to see if it is the notification code BN_CLICKED which is sent when the button is clicked.
The interesting part is after it's certain that the notification code is BN_CLICKED. We want to get the text from the edit box and display it in a message box. We can duplicate the code in the IDM_GETTEXT section above but it doesn't make sense. If we can somehow send a WM_COMMAND message with the low word of wParam containing the value IDM_GETTEXT to our own window procedure, we can avoid code duplication and simplify our program. SendMessage function is the answer. This function sends any message to any window with any wParam and lParam we want. So instead of duplicating the code, we call SendMessage with the parent window handle, WM_COMMAND, IDM_GETTEXT, and 0. This has identical effect to selecting "Get Text" menu item from the menu. The window procedure doesn't perceive any difference between the two.
You should use this technique as much as possible to make your code more organized.
Last but not least, do not forget the TranslateMessage function in the message loop. Since you must type in some text into the edit box, your program must translate raw keyboard input into readable text. If you omit this function, you will not be able to type anything into your edit box.

[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32 Assembly Tutorial 10: Dialog Box as Main Window

Tutorial 10: Dialog Box as Main Window

Now comes the really interesting part about GUI, the dialog box. In this tutorial (and the next), we will learn how to use a dialog box as our main window.

Download the first example here, the second example here.

Theory:

If you play with the examples in the previous tutorial long enough, you 'll find out that you cannot change input focus from one child window control to another with Tab key. The only way you can do that is by clicking the control you want it to gain input focus. This situation is rather cumbersome. Another thing you might notice is that I changed the background color of the parent window to gray instead of normal white as in previous examples. This is done so that the color of the child window controls can blend seamlessly with the color of the client area of the parent window. There is a way to get around this problem but it's not easy. You have to subclass all child window controls in your parent window.
The reason why such inconvenience exists is that child window controls are originally designed to work with a dialog box, not a normal window. The default color of child window controls such as a button is gray because the client area of a dialog box is normally gray so they blend into each other without any sweat on the programmer's part.
Before we get deep into the detail, we should know first what a dialog box is. A dialog box is nothing more than a normal window which is designed to work with child window controls. Windows also provides internal "dialog box manager" which is responsible for most of the keyboard logic such as shifting input focus when the user presses Tab, pressing the default pushbutton if Enter key is pressed, etc so programmers can deal with higher level tasks. Dialog boxes are primarily used as input/output devices. As such a dialog box can be considered as an input/output "black box" meaning that you don't have to know how a dialog box works internally in order to be able to use it, you only have to know how to interact with it. That's a principle of object oriented programming (OOP) called information hiding. If the black box is *perfectly* designed, the user can make use of it without any knowledge on how it operates. The catch is that the black box must be perfect, that's hard to achieve in the real world. Win32 API is also designed as a black box too.
Well, it seems we stray from our path. Let's get back to our subject. Dialog boxes are designed to reduce workload of a programmer. Normally if you put child window controls on a normal window, you have to subclass them and write keyboard logic yourself. But if you put them on a dialog box, it will handle the logic for you. You only have to know how to get the user input from the dialog box or how to send commands to it.
A dialog box is defined as a resource much the same way as a menu. You write a dialog box template describing the characteristics of the dialog box and its controls and then compile the resource script with a resource editor.
Note that all resources are put together in the same resource script file. You can use any text editor to write a dialog box template but I don't recommend it. You should use a resource editor to do the job visually since arranging child window controls on a dialog box is hard to do manually. Several excellent resource editors are available. Most of the major compiler suites include their own resource editors. You can use them to create a resource script for your program and then cut out irrelevant lines such as those related to MFC.
There are two main types of dialog box: modal and modeless. A modeless dialog box lets you change input focus to other window. The example is the Find dialog of MS Word. There are two subtypes of modal dialog box: application modal and system modal. An application modal dialog box doesn't let you change input focus to other window in the same application but you can change the input focus to the window of OTHER application. A system modal dialog box doesn't allow you to change input focus to any other window until you respond to it first.
A modeless dialog box is created by calling CreateDialogParam API function. A modal dialog box is created by calling DialogBoxParam. The only distinction between an application modal dialog box and a system modal one is the DS_SYSMODAL style. If you include DS_SYSMODAL style in a dialog box template, that dialog box will be a system modal one.
You can communicate with any child window control on a dialog box by using SendDlgItemMessage function. Its syntax is like this:
SendDlgItemMessage proto hwndDlg:DWORD,\
idControl:DWORD,\
uMsg:DWORD,\
wParam:DWORD,\
lParam:DWORD
This API call is immensely useful for interacting with a child window control. For example, if you want to get the text from an edit control, you can do this:
call SendDlgItemMessage, hDlg, ID_EDITBOX, WM_GETTEXT, 256, ADDR text_buffer
In order to know which message to send, you should consult your Win32 API reference.
Windows also provides several control-specific API functions to get and set data quickly, for example, GetDlgItemText, CheckDlgButton etc. These control-specific functions are provided for programmer's convenience so he doesn't have to look up the meanings of wParam and lParam for each message. Normally, you should use control-specific API calls when they're available since they make source code maintenance easier. Resort to SendDlgItemMessage only if no control-specific API calls are available.
The Windows dialog box manager sends some messages to a specialized callback function called a dialog box procedure which has the following format:
DlgProc proto hDlg:DWORD ,\
iMsg:DWORD ,\
wParam:DWORD ,\
lParam:DWORD
The dialog box procedure is very similar to a window procedure except for the type of return value which is TRUE/FALSE instead of LRESULT. The internal dialog box manager inside Windows IS the true window procedure for the dialog box. It calls our dialog box procedure with some messages that it received. So the general rule of thumb is that: if our dialog box procedure processes a message,it MUST return TRUE in eax and if it does not process the message, it must return FALSE in eax. Note that a dialog box procedure doesn't pass the messages it does not process to the DefWindowProc call since it's not a real window procedure.
There are two distinct uses of a dialog box. You can use it as the main window of your application or use it as an input device. We 'll examine the first approach in this tutorial.
"Using a dialog box as main window" can be interpreted in two different senses.
  1. You can use the dialog box template as a class template which you register with RegisterClassEx call. In this case, the dialog box behaves like a "normal" window: it receives messages via a window procedure referred to by lpfnWndProc member of the window class, not via a dialog box procedure. The benefit of this approach is that you don't have to create child window controls yourself, Windows creates them for you when the dialog box is created. Also Windows handles the keyboard logic for you such as Tab order etc. Plus you can specify the cursor and icon of your window in the window class structure.

    2. Your program just creates the dialog box without creating any parent window. This approach makes a message loop unnecessary since the messages are sent directly to the dialog box procedure. You don't even have to register a window class!
This tutorial is going to be a long one. I'll present the first approach followed by the second.

Examples:

dialog.asm

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "DLGCLASS",0
MenuName db "MyMenu",0
DlgName db "MyDialog",0
AppName db "Our First Dialog Box",0
TestString db "Wow! I'm in an edit box now",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)

.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hDlg:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,DLGWINDOWEXTRA
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
mov hDlg,eax
invoke ShowWindow, hDlg,SW_SHOWNORMAL
invoke UpdateWindow, hDlg
invoke GetDlgItem,hDlg,IDC_EDIT
invoke SetFocus,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.IF dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Dialog.rc

#include "resource.h"

#define IDC_EDIT 3000
#define IDC_BUTTON 3001
#define IDC_EXIT 3002

#define IDM_GETTEXT 32000
#define IDM_CLEAR 32001
#define IDM_EXIT 32003

MyDialog DIALOG 10, 10, 205, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "Our First Dialog Box"
CLASS "DLGCLASS"
BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13, WS_GROUP
END

MyMenu MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END

Analysis:

Let's analyze this first example.
This example shows how to register a dialog template as a window class and create a "window" from that class. It simplifies your program since you don't have to create the child window controls yourself.
Let's first analyze the dialog template.

MyDialog DIALOG 10, 10, 205, 60

Declare the name of a dialog, in this case, "MyDialog" followed by the keyword "DIALOG". The following four numbers are: x, y , width, and height of the dialog box in dialog box units (not the same as pixels).

STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK

Declare the styles of the dialog box.

CAPTION "Our First Dialog Box"

This is the text that will appear in the dialog box's title bar.

CLASS "DLGCLASS"

This line is crucial. It's this CLASS keyword that allows us to use the dialog box template as a window class. Following the keyword is the name of the "window class"

BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END

The above block defines the child window controls in the dialog box. They're defined between BEGIN and END keywords. Generally the syntax is as follows:

    control-type "text" ,controlID, x, y, width, height [,styles]
control-types are resource compiler's constants so you have to consult the manual.
Now we go to the assembly source code. The interesting part is in the window class structure:
    mov wc.cbWndExtra,DLGWINDOWEXTRA
    mov wc.lpszClassName,OFFSET ClassName
Normally, this member is left NULL, but if we want to register a dialog box template as a window class, we must set this member to the value DLGWINDOWEXTRA. Note that the name of the class must be identical to the one following the CLASS keyword in the dialog box template. The remaining members are initialized as usual. After you fill the window class structure, register it with RegisterClassEx. Seems familiar? This is the same routine you have to do in order to register a normal window class.
    invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
After registering the "window class", we create our dialog box. In this example, I create it as a modeless dialog box with CreateDialogParam function. This function takes 5 parameters but you only have to fill in the first two: the instance handle and the pointer to the name of the dialog box template. Note that the 2nd parameter is not a pointer to the class name.
At this point, the dialog box and its child window controls are created by Windows. Your window procedure will receive WM_CREATE message as usual.
    invoke GetDlgItem,hDlg,IDC_EDIT
    invoke SetFocus,eax
After the dialog box is created, I want to set the input focus to the edit control. If I put these codes in WM_CREATE section, GetDlgItem call will fail since at that time, the child window controls are not created yet. The only way you can do this is to call it after the dialog box and all its child window controls are created. So I put these two lines after the UpdateWindow call. GetDlgItem function gets the control ID and returns the associated control's window handle. This is how you can get a window handle if you know its control ID.

invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF

The program enters the message loop and before we translate and dispatch messages, we call IsDialogMessage function to let the dialog box manager handles the keyboard logic of our dialog box for us. If this function returns TRUE , it means the message is intended for the dialog box and is processed by the dialog box manager. Note another difference from the previous tutorial. When the window procedure wants to get the text from the edit control, it calls GetDlgItemText function instead of GetWindowText. GetDlgItemText accepts a control ID instead of a window handle. That makes the call easier in the case you use a dialog box.

Now let's go to the second approach to using a dialog box as a main window. In the next example, I 'll create an application modal dialog box. You'll not find a message loop or a window procedure because they're not necessary!
dialog.asm (part 2)

.386
.model flat,stdcall
option casemap:none

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
DlgName db "MyDialog",0
AppName db "Our Second Dialog Box",0
TestString db "Wow! I'm in an edit box now",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)

.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
invoke ExitProcess,eax

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSEIF ax==IDM_EXIT
invoke EndDialog, hWnd,NULL
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.if dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
mov eax,FALSE
ret
.ENDIF
mov eax,TRUE
ret
DlgProc endp
end start

dialog.rc (part 2)

#include "resource.h"

#define IDC_EDIT 3000
#define IDC_BUTTON 3001
#define IDC_EXIT 3002

#define IDR_MENU1 3003

#define IDM_GETTEXT 32000
#define IDM_CLEAR 32001
#define IDM_EXIT 32003

MyDialog DIALOG 10, 10, 205, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "Our Second Dialog Box"
MENU IDR_MENU1
BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END

IDR_MENU1 MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END

The analysis follows:

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD

We declare the function prototype for DlgProc so we can refer to it with addr operator in the line below:

invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL

The above line calls DialogBoxParam function which takes 5 parameters: the instance handle, the name of the dialog box template, the parent window handle, the address of the dialog box procedure, and the dialog-specific data. DialogBoxParam creates a modal dialog box. It will not return until the dialog box is destroyed.

.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0

The dialog box procedure looks like a window procedure except that it doesn't receive WM_CREATE message. The first message it receives is WM_INITDIALOG. Normally you can put the initialization code here. Note that you must return the value TRUE in eax if you process the message.
The internal dialog box manager doesn't send our dialog box procedure the WM_DESTROY message by default when WM_CLOSE is sent to our dialog box. So if we want to react when the user presses the close button on our dialog box, we must process WM_CLOSE message. In our example, we send WM_COMMAND message with the value IDM_EXIT in wParam. This has the same effect as when the user selects Exit menu item. EndDialog is called in response to IDM_EXIT.
The processing of WM_COMMAND messages remains the same.
When you want to destroy the dialog box, the only way is to call EndDialog function. Do not try DestroyWindow! EndDialog doesn't destroy the dialog box immediately. It only sets a flag for the internal dialog box manager and continues to execute the next instructions.
Now let's examine the resource file. The notable change is that instead of using a text string as menu name we use a value, IDR_MENU1. This is necessary if you want to attach a menu to a dialog box created with DialogBoxParam. Note that in the dialog box template, you have to add the keyword MENU followed by the menu resource ID.
A difference between the two examples in this tutorial that you can readily observe is the lack of an icon in the latter example. However, you can set the icon by sending the message WM_SETICON to the dialog box during WM_INITDIALOG.

[Iczelion's Win32 Assembly Homepage]


Iczelion's Win32 Assembly Tutorial 10: More about Dialog Box

Tutorial 11: More about Dialog Box

We will learn more about dialog box in this tutorial. Specifically, we will explore the topic of how to use dialog boxs as our input-output devices. If you read the previous tutorial, this one will be a breeze since only a minor modification is all that's needed to be able to use dialog boxes as adjuncts to our main window. Also in this tutorial, we will learn how to use common dialog boxes.

Download the dialog box examples here and here. Download Common Dialog Box example here.

Theory:

Very little is to be said about how to use dialog boxes as input-output devices of our program. Your program creates the main window as usual and when you want to display the dialog box, just call CreateDialogParam or DialogBoxParam. With DialogBoxParam call, you don't have to do anything more, just process the messages in the dialog box procedure. With CreateDialogParam, you must insert IsDialogMessage call in the message loop to let dialog box manager handle the keyboard navigation in your dialog box for you. Since the two cases are trivial, I'll not put the source code here. You can download the examples and examine them yourself, here and here.
Let's go on to the common dialog boxes. Windows has prepared predefined dialog boxes for use by your applications. These dialog boxes exist to provide standardized user interface. They consist of file, print, color, font, and search dialog boxes. You should use them as much as possible. The dialog boxes reside in comdlg32.dll. In order to use them, you have to link to comdlg32.lib. You create these dialog boxes by calling appropriate functions in the common dialog library. For open file dialog, it is GetOpenFileName, for save as dialog it is GetSaveFileName, for print dialog it is PrintDlg and so on. Each one of these functions takes a pointer to a structure as its parameter. You should look them up in Win32 API reference. In this tutorial, I'll demonstrate how to create and use an open file dialog.
Below is the function prototype of GetOpenFileName function:
GetOpenFileName proto lpofn:DWORD
You can see that it receives only one parameter, a pointer to an OPENFILENAME structure. The return value TRUE means the user selected a file to open, it's FALSE otherwise. We will look at OPENFILENAME structure next.
OPENFILENAME STRUCT
lStructSize DWORD ?
hwndOwner HWND ?
hInstance HINSTANCE ?
lpstrFilter LPCSTR ?
lpstrCustomFilter LPSTR ?
nMaxCustFilter DWORD ?
nFilterIndex DWORD ?
lpstrFile LPSTR ?
nMaxFile DWORD ?
lpstrFileTitle LPSTR ?
nMaxFileTitle DWORD ?
lpstrInitialDir LPCSTR ?
lpstrTitle LPCSTR ?
Flags DWORD ?
nFileOffset WORD ?
nFileExtension WORD ?
lpstrDefExt LPCSTR ?
lCustData LPARAM ?
lpfnHook DWORD ?
lpTemplateName LPCSTR ?
OPENFILENAME ENDS
Let's see the meaning of the frequently used members.
lStructSize | The size of the OPENFILENAME structure , in bytes
hwndOwner | The window handle of the open file dialog box.
hInstance | Instance handle of the application that creates the open file dialog box
lpstrFilter | The filter strings in the format of pairs of null terminated strings. The first string in each pair is the description. The second string is the filter pattern. for example: FilterString db "All Files (*.*)",0, "*.*",0 db "Text Files (*.txt)",0,"*.txt",0,0 Note that only the pattern in the second string in each pair is actually used by Windows to filter out the files. Also noted that you have to put an extra 0 at the end of the filter strings to denote the end of it.
nFilterIndex | Specify which pair of the filter strings will be initially used when the open file dialog is first displayed. The index is 1-based, that is the first pair is 1, the second pair is 2 and so on. So in the above example, if we specify nFilterIndex as 2, the second pattern, "*.txt" will be used.
lpstrFile | Pointer to the buffer that contains the filename used to initialize the filename edit control on the dialog box. The buffer should be at least 260 bytes long. After the user selects a file to open, the filename with full path is stored in this buffer. You can extract the information from it later.
nMaxFile | The size of the lpstrFile buffer.
lpstrTitle | Pointer to the title of the open file dialog box
Flags | Determine the styles and characteristics of the dialog box.
nFileOffset | After the user selects a file to open, this member contains the index to the first character of the actual filename. For example, if the full name with path is "c:\windows\system\lz32.dll", the this member will contain the value 18.
nFileExtension | After the user selects a file to open, this member contains the index to the first character of the file extension

Example:

The following program displays an open file dialog box when the user selects File-> Open from the menu. When the user selects a file in the dialog box, the program displays a message box showing the full name, filename,and extension of the selected file.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

.const
IDM_OPEN equ 1
IDM_EXIT equ 2
MAXSIZE equ 260
OUTPUTSIZE equ 512

.data
ClassName db "SimpleWinClass",0
AppName db "Our Main Window",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
OurTitle db "-=Our First Open File Dialog Box=-: Choose the file to open",0
FullPathName db "The Full Filename with Path is: ",0
FullName db "The Filename is: ",0
ExtensionName db "The Extension is: ",0
OutputString db OUTPUTSIZE dup(0)
CrLf db 0Dh,0Ah,0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if ax==IDM_OPEN
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hwndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
mov ofn.lpstrTitle, OFFSET OurTitle
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke lstrcat,offset OutputString,OFFSET FullPathName
invoke lstrcat,offset OutputString,ofn.lpstrFile
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset FullName
mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileOffset
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset ExtensionName
mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileExtension
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax
invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK
invoke RtlZeroMemory,offset OutputString,OUTPUTSIZE
.endif
.else
invoke DestroyWindow, hWnd
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analysis:

mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hwndOwner
push hInstance
pop ofn.hInstance

We fill in the routine members of ofn structures.

mov ofn.lpstrFilter, OFFSET FilterString

This FilterString is the filename filter that we specify as follows:

FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
Note that All four strings are zero terminated. The first string is the description of the following string. The actual pattern is the even number string, in this case, "*.*" and "*.txt". Actually we can specify any pattern we want here. We MUST put an extra zero after the last pattern string to denote the end of the filter string. Don't forget this else your dialog box will behave strangely.

mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE

We specify where the dialog box will put the filename that the user selects. Note that we must specify its size in nMaxFile member. We can later extract the filename from this buffer.

mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY

Flags specifies the characteristics of the dialog box.
OFN_FILEMUSTEXIST and OFN_PATHMUSTEXIST flags demand that the filename and path that the user types in the filename edit control MUST exist.
OFN_LONGNAMES flag tells the dialog box to show long filenames.
OFN_EXPLORER flag specifies that the appearance of the dialog box must be explorer-like.
OFN_HIDEREADONLY flag hides the read-only checkbox on the dialog box.
There are many more flags that you can use. Consult your Win32 API reference.

mov ofn.lpstrTitle, OFFSET OurTitle

Specify the title of the dialog box.

invoke GetOpenFileName, ADDR ofn

Call the GetOpenFileName function. Passing the pointer to the ofn structure as its parameter.
At this time, the open file dialog box is displayed on the screen. The function will not return until the user selects a file to open or presses the cancel button or closes the dialog box.
It 'll return the value TRUE in eax if the user selects a file to open. It returns FALSE otherwise.

.if eax==TRUE
invoke lstrcat,offset OutputString,OFFSET FullPathName
invoke lstrcat,offset OutputString,ofn.lpstrFile
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset FullName

In case the user selects a file to open, we prepare an output string to be displayed in a message box. We allocate a block of memory in OutputString variable and then we use an API function, lstrcat, to concatenate the strings together. In order to put the strings into several lines, we must separate each line with a carriage return-line feed pair.

mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileOffset
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax

The above lines require some explanation. nFileOffset contains the index into the ofn.lpstrFile. But you cannot add them together directly since nFileOffset is a WORD-sized variable and lpstrFile is a DWORD-sized one. So I have to put the value of nFileOffset into the low word of ebx and add it to the value of lpstrFile.

invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK

We display the string in a message box.

invoke RtlZerolMemory,offset OutputString,OUTPUTSIZE

We must *clear* the OutputString before we can fill in another string. So we use RtlZeroMemory function to do the job.

[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32 Assembly Tutorial 12: Memory Management and File I/O

Tutorial 12: Memory Management and File I/O

We will learn the rudimentary of memory management and file i/o operation in this tutorial. In addition we'll use common dialog boxes as input-output devices.

Download the example here.

Theory:

Memory management under Win32 from the application's point of view is quite simple and straightforward. Each process owns a 4 GB memory address space. The memory model used is called flat memory model. In this model, all segment registers (or selectors) point to the same starting address and the offset is 32-bit so an application can access memory at any point in its own address space without the need to change the value of selectors. This simplifies memory management a lot. There's no "near" or "far" pointer anymore.
Under Win16, there are two main categories of memory API functions: Global and Local. Global-type API calls deal with memory allocated in other segments thus they're "far" memory functions. Local-type API calls deal with the local heap of the process so they're "near" memory functions. Under Win32, these two types are identical. Whether you call GlobalAlloc or LocalAlloc, you get the same result.
Steps in allocating and using memory are as follows:
  1. Allocate a block of memory by calling GlobalAlloc. This function returns a handle to the requested memory block.
  2. "Lock" the memory block by calling GlobalLock. This function accepts a handle to the memory block and returns a pointer to the memory block.
  3. You can use the pointer to read or write memory.
  4. "Unlock" the memory block by calling GlobalUnlock . This function invalidates the pointer to the memory block.
  5. Free the memory block by calling GlobalFree. This function accepts the handle to the memory block.
You can also substitute "Global" by "Local" such as LocalAlloc, LocalLock,etc.
The above method can be further simplified by using a flag in GlobalAlloc call, GMEM_FIXED. If you use this flag, the return value from Global/LocalAlloc will be the pointer to the allocated memory block, not the memory block handle. You don't have to call Global/LocalLock and you can pass the pointer to Global/LocalFree without calling Global/LocalUnlock first. But in this tutorial, I'll use the "traditional" approach since you may encounter it when reading the source code of other programs.

File I/O under Win32 bears remarkable semblance to that under DOS. The steps needed are the same. You only have to change interrupts to API calls and it's done. The required steps are the followings:

  1. Open or Create the file by calling CreateFile function. This function is very versatile: in addition to files, it can open communication ports, pipes, disk drives or console. On success, it returns a handle to file or device. You can then use this handle to perform operations on the file or device.

    2. Move the file pointer to the desired location by calling SetFilePointer.
  2. Perform read or write operation by calling ReadFile or WriteFile. These functions transfer data from a block of memory to or from the file. So you have to allocate a block of memory large enough to hold the data.
  3. Close the file by calling CloseHandle. This function accepts the file handle.

Content:

The program listed below displays an open file dialog box. It lets the user select a text file to open and shows the content of that file in an edit control in its client area. The user can modify the text in the edit control as he wishes, and can choose to save the content in a file.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260
MEMSIZE equ 65535

EditID equ 1 ; ID of the edit control

.data
ClassName db "Win32ASMEditClass",0
AppName db "Win32 ASM Edit",0
EditClass db "edit",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndEdit HWND ? ; Handle to the edit control
hFile HANDLE ? ; File handle
hMemory HANDLE ? ;handle to the allocated memory block
pMemory DWORD ? ;pointer to the allocated memory block
SizeReadWrite DWORD ? ; number of bytes actually read or write

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:SDWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc uses ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
0,0,0,hWnd,EditID,\
hInstance,NULL
mov hwndEdit,eax
invoke SetFocus,hwndEdit
;==============================================
; Initialize the members of OPENFILENAME structure
;==============================================
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
.ELSEIF uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0ffffh
invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE
.ELSEIF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
invoke SetFocus,hwndEdit
.elseif ax==IDM_SAVE
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetSaveFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
invoke SetFocus,hwndEdit
.else
invoke DestroyWindow, hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analysis:

invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
0,0,0,hWnd,EditID,\
hInstance,NULL
mov hwndEdit,eax

In WM_CREATE section, we create an edit control. Note that the parameters that specify x, y, width,height of the control are all zeroes since we will resize the control later to cover the whole client area of the parent window.
Note that in this case, we don't have to call ShowWindow to make the edit control appear on the screen because we include WS_VISIBLE style. You can use this trick in the parent window too.

;==============================================
; Initialize the members of OPENFILENAME structure
;==============================================
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE

After creating the edit control, we take this time to initialize the members of ofn. Because we want to reuse ofn in the save as dialog box too, we fill in only the *common* members that're used by both GetOpenFileName and GetSaveFileName.
WM_CREATE section is a great place to do once-only initialization.

.ELSEIF uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0ffffh
invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE

We receive WM_SIZE messages when the size of the client area of our main window changes. We also receive it when the window is first created. In order to be able to receive this message, the window class styles must include CS_VREDRAW and CS_HREDRAW styles. We use this opportunity to resize our edit control to the same size as the client area of the parent window. First we have to know the current width and height of the client area of the parent window. We get this info from lParam. The high word of lParam contains the height and the low word of lParam the width of the client area. We then use the information to resize the edit control by calling MoveWindow function which, in addition to changing the position of the window, can alter the size too.

.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn

When the user selects File/Open menu item, we fill in the Flags member of ofn structure and call GetOpenFileName function to display the open file dialog box.

.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax

After the user selects a file to open, we call CreateFile to open the file. We specifies that the function should try to open the file for read and write. After the file is opened, the function returns the handle to the opened file which we store in a global variable for future use. This function has the following syntax:

CreateFile proto lpFileName:DWORD,\
dwDesiredAccess:DWORD,\
dwShareMode:DWORD,\
lpSecurityAttributes:DWORD,\
dwCreationDistribution:DWORD\,
dwFlagsAndAttributes:DWORD\,
hTemplateFile:DWORD

dwDesiredAccess specifies which operation you want to perform on the file.

  • 0 Open the file to query its attributes. You have to rights to read or write the data.
  • GENERIC_READ Open the file for reading.
  • GENERIC_WRITE Open the file for writing.
dwShareMode specifies which operation you want to allow other processes to perform on the file that 's being opened.
  • 0 Don't share the file with other processes.
  • FILE_SHARE_READ allow other processes to read the data from the file being opened
  • FILE_SHARE_WRITE allow other processes to write data to the file being opened.
lpSecurityAttributes has no significance under Windows 95.
dwCreationDistribution specifies the action CreateFile will perform when the file specified in lpFileName exists or when it doesn't exist.
  • CREATE_NEW Creates a new file. The function fails if the specified file already exists.
  • CREATE_ALWAYS Creates a new file. The function overwrites the file if it exists.
  • OPEN_EXISTING Opens the file. The function fails if the file does not exist.
  • OPEN_ALWAYS Opens the file, if it exists. If the file does not exist, the function creates the file as if dwCreationDistribution were CREATE_NEW.
  • TRUNCATE_EXISTING Opens the file. Once opened, the file is truncated so that its size is zero bytes. The calling process must open the file with at least GENERIC_WRITE access. The function fails if the file does not exist.
dwFlagsAndAttributes specifies the file attributes
  • FILE_ATTRIBUTE_ARCHIVE The file is an archive file. Applications use this attribute to mark files for backup or removal.
  • FILE_ATTRIBUTE_COMPRESSED The file or directory is compressed. For a file, this means that all of the data in the file is compressed. For a directory, this means that compression is the default for newly created files and subdirectories.
  • FILE_ATTRIBUTE_NORMAL The file has no other attributes set. This attribute is valid only if used alone.
  • FILE_ATTRIBUTE_HIDDEN The file is hidden. It is not to be included in an ordinary directory listing.
  • FILE_ATTRIBUTE_READONLY The file is read only. Applications can read the file but cannot write to it or delete it.
  • FILE_ATTRIBUTE_SYSTEM The file is part of or is used exclusively by the operating system.
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax

When the file is opened, we allocate a block of memory for use by ReadFile and WriteFile functions. We specify GMEM_MOVEABLE flag to let Windows move the memory block around to consolidate memory. GMEM_ZEROINIT flag tells GlobalAlloc to fill the newly allocated memory block with zeroes.
When GlobalAlloc returns successfully, eax contains the handle to the allocated memory block. We pass this handle to GlobalLock function which returns a pointer to the memory block.

invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory

When the memory block is ready for use, we call ReadFile function to read data from the file. When a file is first opened or created, the file pointer is at offset 0. So in this case, we start reading from the first byte in the file onwards. The first parameter of ReadFile is the handle of the file to read, the second is the pointer to the memory block to hold the data, next is the number of bytes to read from the file, the fourth param is the address of the variable of DWORD size that will be filled with the number of bytes actually read from the file.
After we fill the memory block with the data, we put the data into the edit control by sending WM_SETTEXT message to the edit control with lParam containing the pointer to the memory block. After this call, the edit control shows the data in its client area.

invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif

At this point, we have no need to keep the file open any longer since our purpose is to write the modified data from the edit control to another file, not the original file. So we close the file by calling CloseHandle with the file handle as its parameter. Next we unlock the memory block and free it. Actually you don't have to free the memory at this point, you can reuse the memory block during the save operation later. But for demonstration purpose , I choose to free it here.

invoke SetFocus,hwndEdit

When the open file dialog box is displayed on the screen, the input focus shifts to it. So after the open file dialog is closed, we must move the input focus back to the edit control.
This end the read operation on the file. At this point, the user can edit the content of the edit control.And when he wants to save the data to another file, he must select File/Save as menuitem which displays a save as dialog box. The creation of the save as dialog box is not much different from the open file dialog box. In fact, they differ in only the name of the functions, GetOpenFileName and GetSaveFileName. You can reuse most members of the ofn structure too except the Flags member.

mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY

In our case, we want to create a new file, so OFN_FILEMUSTEXIST and OFN_PATHMUSTEXIST must be left out else the dialog box will not let us create a file that doesn't already exist.
The dwCreationDistribution parameter of the CreateFile function must be changed to CREATE_NEW since we want to create a new file.
The remaining code is identical to those in the open file section except the following:

invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL

We send WM_GETTEXT message to the edit control to copy the data from it to the memory block we provide, the return value in eax is the length of the data inside the buffer. After the data is in the memory block, we write them to the new file.

[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32 Assembly Tutorial 13: Memory Mapped Files

Tutorial 13: Memory Mapped Files

I'll show you what memory mapped files are and how to use them to your advantages. Using a memory mapped file is quite easy as you'll see in this tutorial.

Download the example here.

Theory:

If you examine the example in the previous tutorial closely, you'll find that it has a serious shortcoming: what if the file you want to read is larger than the allocated memory block? or what if the string you want to search for is cut off in half at the end of the memory block? The traditional answer for the first question is that you should repeatedly read in the data from the file until the end of file is encountered. The answer to the second question is that you should prepare for the special case at the end of the memory block. This is called a boundary value problem. It presents headaches to programmers and causes innumerable bugs.
It would be nice if we can allocate a very large block of memory, enough to store the whole file but our program would be a resource hog. File mapping to the rescue. By using file mapping, you can think of the whole file as being already loaded into memory and you can use a memory pointer to read or write data from the file. As easy as that. No need to use memory API functions and separate File I/O API functions anymore, they are one and the same under file mapping. File mapping is also used as a means to share data between processes. Using file mapping in this way, there's no actual file involved. It's more like a reserved memory block that every process can *see*. But sharing data between processes is a delicate subject, not to be treated lightly. You have to implement process and thread synchronization else your applications will crash in very short order.
We will not touch the subject of file mapping as a means to create a shared memory region in this tutorial. We'll concentrate on how to use file mapping as a means to "map" a file into memory. In fact, the PE loader uses file mapping to load executable files into memory. It is very convenient since only the necessary portions can be selectively read from the file on the disk. Under Win32, you should use file mapping as much as possible.
There are some limitation to file mapping though. Once you create a memory mapped file, its size cannot be changed during that session. So file mapping is great for read-only files or file operations that don't affect the file size. That doesn't mean that you cannot use file mapping if you want to increase the file size. You can estimate the new size and create the memory mapped file based on the new size and the file will grow to that size. It's just inconvenient, that's all.
Enough for the explanation. Let's dive into implementation of file mapping. In order to use file mapping, these steps must be performed:
  1. call CreateFile to open the file you want to map.
  2. call CreateFileMapping with the file handle returned by CreateFile as one of its parameter. This function creates a file mapping object from the file opened by CreateFile.
  3. call MapViewOfFile to map a selected file region or the whole file to memory. This function returns a pointer to the first byte of the mapped file region.
  4. Use the pointer to read or write the file
  5. call UnmapViewOfFile to unmap the file.
  6. call CloseHandle with the handle to the mapped file as the parameter to close the mapped file.
  7. call CloseHandle again this time with the file handle returned by CreateFile to close the actual file.

Example:

The program listed below lets you open a file via an open file dialog box. It opens the file using file mapping, if it's successful, the window caption is changed to the name of the opened file. You can save the file in another name by select File/Save as menuitem. The program will copy the whole content of the opened file to the new file. Note that you don't have to call GlobalAlloc to allocate a memory block in this program.

.386
.model flat,stdcall
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260

.data
ClassName db "Win32ASMFileMappingClass",0
AppName db "Win32 ASM File Mapping Example",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
hMapFile HANDLE 0 ; Handle to the memory mapped file, must be
;initialized with 0 because we also use it as
;a flag in WM_DESTROY section too

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hFileRead HANDLE ? ; Handle to the source file
hFileWrite HANDLE ? ; Handle to the output file
hMenu HANDLE ?
pMemory DWORD ? ; pointer to the data in the source file
SizeWritten DWORD ? ; number of bytes actually written by WriteFile

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke GetMenu,hWnd ;Obtain the menu handle
mov hMenu,eax
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFileRead,eax
invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL
mov hMapFile,eax
mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
.endif
.elseif ax==IDM_SAVE
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetSaveFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFileWrite,eax
invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
mov pMemory,eax
invoke GetFileSize,hFileRead,NULL
invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL
invoke UnmapViewOfFile,pMemory
call CloseMapFile
invoke CloseHandle,hFileWrite
invoke SetWindowText,hWnd,ADDR AppName
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
.endif
.else
invoke DestroyWindow, hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp

end start

Analysis:

invoke CreateFile,ADDR buffer,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL

When the user selects a file in the open file dialog, we call CreateFile to open it. Note that we specify GENERIC_READ to open this file for read-only access and dwShareMode is zero because we don't want some other process to modify the file during our operation.

invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL

Then we call CreateFileMapping to create a memory mapped file from the opened file. CreateFileMapping has the following syntax:

CreateFileMapping proto hFile:DWORD,\
lpFileMappingAttributes:DWORD,\
flProtect:DWORD,\
dwMaximumSizeHigh:DWORD,\
dwMaximumSizeLow:DWORD,\
lpName:DWORD

You should know first that CreateFileMapping doesn't have to map the whole file to memory. You can use this function to map only a part of the actual file to memory. You specify the size of the memory mapped file in dwMaximumSizeHigh and dwMaximumSizeLow params. If you specify the size that 's larger than the actual file, the actual file will be expanded to the new size. If you want the memory mapped file to be the same size as the actual file, put zeroes in both params.
You can use NULL in lpFileMappingAttributes parameter to have Windows creates a memory mapped file with default security attributes.
flProtect defines the protection desired for the memory mapped file. In our example, we use PAGE_READONLY to allow only read operation on the memory mapped file. Note that this attribute must not contradict the attribute used in CreateFile else CreateFileMapping will fail.
lpName points to the name of the memory mapped file. If you want to share this file with other process, you must provide it a name. But in our example, our process is the only one that uses this file so we ignore this parameter.

mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax

If CreateFileMapping is successful, we change the window caption to the name of the opened file. The filename with full path is stored in buffer, we want to display only the filename in the caption so we must add the value of nFileOffset member of the OPENFILENAME structure to the address of buffer.

invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED

As a precaution, we don't want the user to open multiple files at once, so we gray out the Open menu item and enable the Save menu item. EnableMenuItem is used to change the attribute of menu item.
After this, we wait for the user to select File/Save as menu item or close our program. If the user chooses to close our program, we must close the memory mapped file and the actual file like the code below:

.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL

In the above code snippet, when the window procedure receives the WM_DESTROY message, it checks the value of hMapFile first whether it is zero or not. If it's not zero, it calls CloseMapFile function which contains the following code:

CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp

CloseMapFile closes the memory mapped file and the actual file so that there 'll be no resource leakage when our program exits to Windows.
If the user chooses to save that data to another file, the program presents him with a save as dialog box. After he types in the name of the new file, the file is created by CreateFile function.

invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
mov pMemory,eax

Immediately after the output file is created, we call MapViewOfFile to map the desired portion of the memory mapped file into memory. This function has the following syntax:

MapViewOfFile proto hFileMappingObject:DWORD,\
dwDesiredAccess:DWORD,\
dwFileOffsetHigh:DWORD,\
dwFileOffsetLow:DWORD,\
dwNumberOfBytesToMap:DWORD

dwDesiredAccess specifies what operation we want to do to the file. In our example, we want to read the data only so we use FILE_MAP_READ.
dwFileOffsetHigh and dwFileOffsetLowspecify the starting file offset of the file portion that you want to map into memory. In our case, we want to read in the whole file so we start mapping from offset 0 onwards.
dwNumberOfBytesToMap specifies the number of bytes to map into memory. If you want to map the whole file (specified by CreateFileMapping), pass 0 to MapViewOfFile.
After calling MapViewOfFile, the desired portion is loaded into memory. You'll be given the pointer to the memory block that contains the data from the file.

invoke GetFileSize,hFileRead,NULL

Find out how large the file is. The file size is returned in eax. If the file is larger than 4 GB, the high DWORD of the file size is stored in FileSizeHighWord. Since we don't expect to handle such large file, we can ignore it.

invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL

Write the data that is mapped into memory into the output file.

invoke UnmapViewOfFile,pMemory

When we're through with the input file, unmap it from memory.

call CloseMapFile
invoke CloseHandle,hFileWrite

And close all the files.

invoke SetWindowText,hWnd,ADDR AppName

Restore the original caption text.

invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED

Enable the Open menu item and gray out the Save As menu item.

[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32 Assembly Tutorial 14: Process

Tutorial 14: Process

We will learn what a process is and how to create and terminate it.

Download the example here.

Preliminary:

What is a process? I quote this definition from Win32 API reference:
"A process is an executing application that consists of a private virtual address space, code, data, and other operating system resources, such as files, pipes, and synchronization objects that are visible to the process."
As you can see from the definition above, a process "owns" several objects: the address space, the executing module(s), and anything that the executing modules create or open. At the minimum, a process must consist of an executing module, a private address space and a thread. Every process must have at least one thread. What's a thread? A thread is actually an execution queue. When Windows first creates a process, it creates only one thread per process. This thread usually starts execution from the first instruction in the module. If the process later needs more threads, it can explicitly create them.
When Windows receives a command to create a process, it creates the private memory address space for the process and then it maps the executable file into the space. After that it creates the primary thread for the process.
Under Win32, you can also create processes from your own programs by calling CreateProcess function. CreateProcess has the following syntax:

CreateProcess proto lpApplicationName:DWORD,\
lpCommandLine:DWORD,\
lpProcessAttributes:DWORD,\
lpThreadAttributes:DWORD,\
bInheritHandles:DWORD,\
dwCreationFlags:DWORD,\
lpEnvironment:DWORD,\
lpCurrentDirectory:DWORD,\
lpStartupInfo:DWORD,\
lpProcessInformation:DWORD

Don't be alarmed by the number of parameters. We can ignore most of them.

lpApplicationName --> The name of the executable file with or without pathname that you want to execute. If this parameter is null, you must provide the name of the executable file in the lpCommandLine parameter.
lpCommandLine --> The command line arguments to the program you want to execute. Note that if the lpApplicationName is NULL, this parameter must contain the name of the executable file too. Like this: "notepad.exe readme.txt"
lpProcessAttributes and lpthreadAttributes --> Specify the security attributes for the process and the primary thread. If they're NULLs, the default security attributes are used.
bInheritHandles --> A flag that specify if you want the new process to inherit all opened handles from your process.
dwCreationFlags --> Several flags that determine the behavior of the process you want to created, such as, do you want to process to be created but immediately suspended so that you can examine or modify it before it runs? You can also specify the priority class of the thread(s) in the new process. This priority class is used to determine the scheduling priority of the threads within the process. Normally we use NORMAL_PRIORITY_CLASS flag.
lpEnvironment --> A pointer to the environment block that contains several environment strings for the new process. If this parameter is NULL, the new process inherits the environment block from the parent process.
lpCurrentDirectory --> A pointer to the string that specifies the current drive and directory for the child process. NULL if you want the child process to inherit from the parent process.
lpStartupInfo --> Points to a STARTUPINFO structure that specifies how the main window for the new process should appear. The STARTUPINFO structure contains many members that specifies the appearance of the main window of the child process. If you don't want anything special, you can fill the STARTUPINFO structure with the values from the parent process by calling GetStartupInfo function.
lpProcessInformation --> Points to a PROCESS_INFORMATION structure that receives identification information about the new process. The PROCESS_INFORMATION structure has the following members:

PROCESS_INFORMATION STRUCT
hProcess HANDLE ? ; handle to the child process
hThread HANDLE ? ; handle to the primary thread of the child process
dwProcessId DWORD ? ; ID of the child process
dwThreadId DWORD ? ; ID of the primary thread of the child process
PROCESS_INFORMATION ENDS
Process handle and process ID are two different things. A process ID is a unique identifier for the process in the system. A process handle is a value returned by Windows for use with other process-related API functions. A process handle cannot be used to identify a process since it's not unique.

After the CreateProcess call, a new process is created and the CreateProcess call return immediately. You can check if the new process is still active by calling GetExitCodeProcess function which has the following syntax:

GetExitCodeProcess proto hProcess:DWORD, lpExitCode:DWORD

If this call is successful, lpExitCode contains the termination status of the process in question. If the value in lpExitCode is equal to STILL_ACTIVE, then that process is still running.

You can forcibly terminate a process by calling TerminateProcess function. It has the following syntax:

TerminateProcess proto hProcess:DWORD, uExitCode:DWORD

You can specify the desired exit code for the process, any value you like. TerminateProcess is not a clean way to terminate a process since any dll attached to the process will not be notified that the process was terminated.

Example:

The following example will create a new process when the user selects the "create process" menu item. It will attempt to execute "msgbox.exe". If the user wants to terminate the new process, he can select the "terminate process" menu item. The program will check first if the new process is already destroyed, if it is not, the program will call TerminateProcess function to destroy the new process.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.const
IDM_CREATE_PROCESS equ 1
IDM_TERMINATE equ 2
IDM_EXIT equ 3

.data
ClassName db "Win32ASMProcessClass",0
AppName db "Win32 ASM Process Example",0
MenuName db "FirstMenu",0
processInfo PROCESS_INFORMATION <>
programname db "msgbox.exe",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HANDLE ?
ExitCode DWORD ? ; contains the process exitcode status from GetExitCodeProcess call.

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL startInfo:STARTUPINFO
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_INITMENUPOPUP
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_CREATE_PROCESS
.if processInfo.hProcess!=0
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_PRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread
.elseif ax==IDM_TERMINATE
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if ExitCode==STILL_ACTIVE
invoke TerminateProcess,processInfo.hProcess,0
.endif
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analysis:

The program creates the main window and retrieves the menu handle for future use. It then waits for the user to select a command from the menu. When the user selects "Process" menu item in the main menu, we process WM_INITMENUPOPUP message to modify the menu items inside the popup menu before it's displayed.

.ELSEIF uMsg==WM_INITMENUPOPUP
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif

Why do we want to process this message? Because we want to prepare the menu items in the popup menu before the user can see them. In our example, if the new process is not started yet, we want to enable the "start process" and gray out the "terminate process" menu items. We do the reverse if the new process is already active.
We first check if the new process is still running by calling GetExitCodeProcess function with the process handle that was filled in by CreateProcess function. If GetExitCodeProcess returns FALSE, it means the process is not started yet so we gray out the "terminate process" menu item. If GetExitCodeProcess returns TRUE, we know that a new process has been started, but we have to check further if it is still running. So we compare the value in ExitCode to the value STILL_ACTIVE, if they're equal, the process is still running: we must gray out the "start process" menu item since we don't want to start several concurrent processes.

.if ax==IDM_CREATE_PROCESS
.if processInfo.hProcess!=0
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_PRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread

When the user selects "start process" menu item, we first check if hProcess member of PROCESS_INFORMATION structure is already closed. If this is the first time, the value of hProcess will always be zero since we define PROCESS_INFORMATION structure in .data section. If the value of hProcess member is not 0, it means the child process has exited but we haven't closed its process handle yet. So this is the time to do it.
We call GetStartupInfo function to fill in the startupinfo structure that we will pass to CreateProcess function. After that we call CreateProcess function to start the new process. Note that I haven't checked the return value of CreateProcess because it will make the example more complex. In real life, you should check the return value of CreateProcess. Immediately after CreateProcess, we close the primary thread handle returned in processInfo structure. Closing the handle doesn't mean we terminate the thread, only that we don't want to use the handle to refer to the thread from our program. If we don't close it, it will cause a resource leak.

.elseif ax==IDM_TERMINATE
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if ExitCode==STILL_ACTIVE
invoke TerminateProcess,processInfo.hProcess,0
.endif
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0

When the user selects "terminate process" menu item, we check if the new process is still active by calling GetExitCodeProcess function. If it's still active, we call TerminateProcess function to kill the process. Also we close the child process handle since we have no need for it anymore.

[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32 Assembly Tutorial 15: Multithreading Programming

Tutorial 15: Multithreading Programming

We will learn how to create a multithreading program in this tutorial. We also study the communication methods between the threads.

Download the example here.

Theory:

In previous tutorial, you learn the a process consists of at least one thread: the primary thread. A thread is a chain of execution. You can also create additional threads in your program. You can view multithreading as multitasking within one program. In term of implementation, a thread is a function that runs concurrently with the main program. You can run several instances of the same function or you can run several functions simultaneously depending on your requirement. Multithreading is specific to Win32, no Win16 counterpart exists.
Threads run in the same process so they can access any resources in the process such as global variables, handles etc. However, each thread has its own stack so local variables in each thread are private. Each thread also owns its private register set so when Windows switches to other threads, the thread can "remember" its last status and can "resume" the task when it gains control again. This is handled internally by Windows.
We can divide threads into two caterories:
  1. User interface thread: This type of thread creates its own window so it receives windows messages. It can respond to the user via its own window hence the name. This type of thread is subject to Win16 Mutex rule which allows only one user interface thread in 16-bit user and gdi kernel. While a user interface thread is executing code in 16-bit user and gdi kernel, other UI threads cannot use the service of the 16-bit user and gdi kernel. Note that this Win16 Mutex is specific to Windows 95 since underneath, Windows 95 API functions thunk down to 16-bit code. Windows NT has no Win16 Mutex so the user interface threads under NT work more smoothly than under Windows 95.
  2. Worker thread: This type of thread does not create a window so it cannot receive any windows message. It exists primarily to do the assigned job in the background hence the name worker thread.
I advise the following strategy when using multithreading capability of Win32: Let the primary thread do user interface stuff and the other threads do the hard work in the background. In this way, the primary thread is like a Governor, other threads are like the Governor's staff. The Governor delegates jobs to his staff while he maintains contact with the public. The Governor staff obediently performs the work and reports back to the Governor. If the Governor were to perform every task himself, he would not be able to give much attention to the public or the press. That's akin to a window which is busy doing a lengthy job in its primary thread: it doesn't respond to the user until the job is completed. Such a program can benefit from creating an additonal thread which is responsible for the lengthy job, allowing the primary thread to respond to the user's commands.
We can create a thread by calling CreateThread function which has the following syntax:

CreateThread proto lpThreadAttributes:DWORD,\
dwStackSize:DWORD,\
lpStartAddress:DWORD,\
lpParameter:DWORD,\
dwCreationFlags:DWORD,\
lpThreadId:DWORD

CreateThread function looks a lot like CreateProcess.
lpThreadAttributes --> You can use NULL if you want the thread to have default security descriptor.
dwStackSize --> specify the stack size of the thread. If you want the thread to have the same stack size as the primary thread, use NULL as this parameter.
lpStartAddress--> Address of the thread function.It's the function that will perform the work of the thread. This function MUST receive one and only one 32-bit parameter and return a 32-bit value.
lpParameter --> The parameter you want to pass to the thread function.
dwCreationFlags --> 0 means the thread runs immediately after it's created. The opposite is CREATE_SUSPENDED flag.
lpThreadId --> CreateThread function will fill the thread ID of the newly created thread at this address.

If CreateThread call is sucessful, it returns the handle of the newly created thread. Otherwise, it returns NULL.
The thread function runs as soon as CreateThread call is success ful unless you specify CREATE_SUSPENDED flag in dwCreationFlags. In that case, the thread is suspended until ResumeThread function is called.
When the thread function returns with ret instruction, Windows calls ExitThread function for the thread function implicitly. You can call ExitThread function with in your thread function yourself but there' s little point in doing so.
You can retrieve the exit code of a thread by calling GetExitCodeThread function.
If you want to terminate a thread from other thread, you can call TerminateThread function. But you should use this function under extreme circumstance since this function terminates the thread immediately without giving the thread any chance to clean up after itself.

Now let's move to the communication methods between threads.
There are three of them:

  • Using global variables
  • Windows messages
  • Event
Threads share the process's resources including global variables so the threads can use global varibles to communicate with each other. However this method must be used with care. Thread synchronization must enter into consideration. For example, if two threads use the same structure of 10 members , what happens when Windows suddenly yanks the control from one of the thread when it was in the middle of updating the structure? The other thread will be left with an inconsistent data in the structure! Don't make any mistake, multithreading programs are harder to debug and maintain. This sort of bug seems to happen at random which is very hard to track down.
You can also use Windows messages to communicate between threads. If the threads are all user interface ones, there's no problem: this method can be used as a two-way communication. All you have to do is defining one or more custom windows messages that are meaningful to the threads. You define a custom message by using WM_USER message as the base value say , you can define it like this:

WM_MYCUSTOMMSG equ WM_USER+100h

Windows will not use any value from WM_USER upward for its own messages so you can use the value WM_USER and above as your own custom message value.
If one of the thread is a user interface thread and the other is a worker one, you cannot use this method as two-way communication since a worker thread doesn't have its own window so it doesn't have a message queue. You can use the following scheme:

User interface Thread ------> global variable(s)----> Worker thread
Worker Thread ------> custom window message(s) ----> User interface Thread

In fact, we will use this method in our example.
The last communication method is an event object. You can view an event object as a kind of flag. If the event object is in "unsignalled" state, the thread is dormant or sleeping, in this state, the thread doesn't receive CPU time slice. When the event object is in "signalled" state,Windows "wakes up" the thread and it starts performing the assigned task.

Example:

You should download the example zip file and run thread1.exe. Click the "Savage Calculation" menu item. This will instruct the program to perform "add eax,eax " for 600,000,000 times. Note that during that time, you cannot do anything with the main window: you cannot move it, you cannot activate its menu, etc. When the calculation is completed, a message box appears. After that the window accepts your command normally.
To avoid this type of inconveniece to the user, we can move the "calculation" routine into a separate worker thread and let the primary thread continue with its user interface task. You can see that even though the main window responds more slowly than usual, it still responds

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.const
IDM_CREATE_THREAD equ 1
IDM_EXIT equ 2
WM_FINISH equ WM_USER+100h

.data
ClassName db "Win32ASMThreadClass",0
AppName db "Win32 ASM MultiThreading Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
ThreadID DWORD ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_CREATE_THREAD
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
0,\
ADDR ThreadID
invoke CloseHandle,eax
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSEIF uMsg==WM_FINISH
invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

ThreadProc PROC USES ecx Param:DWORD
mov ecx,600000000
Loop1:
add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out:
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc ENDP

end start

Analysis:

The main program presents the user with a normal window with a menu. If the user selects "Create Thread" menu item, the program creates a thread as below:

.if ax==IDM_CREATE_THREAD
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax

The above function creates a thread that will run a procedure named ThreadProc concurrently with the primary thread. After the successful call, CreateThread returns immediately and ThreadProc begins to run. Since we do not use thread handle, we should close it else there'll be some leakage of memory. Note that closing the thread handle doesn't terminate the thread. Its only effect is that we cannot use the thread handle anymore.

ThreadProc PROC USES ecx Param:DWORD
mov ecx,600000000
Loop1:
add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out:
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc ENDP

As you can see, ThreadProc performs a savage calculation which takes quite a while to finish and when it finishs it posts a WM_FINISH message to the main window. WM_FINISH is our custom message defined like this:

    WM_FINISH equ WM_USER+100h
You don't have to add WM_USER with 100h but it's safer to do so.
The WM_FINISH message is meaningful only within our program. When the main window receives the WM_FINISH message, it respons by displaying a message box saying that the calculation is completed.
You can create several threads in succession by selecting "Create Thread" several times.
In this example, the communication is one-way in that only the thread can notify the main window. If you want the main thread to send commands to the worker thread, you can so as follows:
  • add a menu item saying something like "Kill Thread" in the menu
  • a global variable which is used as a command flag. TRUE=Stop the thread, FALSE=continue the thread
  • Modify ThreadProc to check the value of the command flag in the loop.
When the user selects "Kill Thread" menu item, the main program will set the value TRUE in the command flag. When ThreadProc sees that the value of the command flag is TRUE, it exits the loop and returns thus ends the thread.
[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32 Assembly Tutorial 16: Event Object

Tutorial 16: Event Object

We will learn what an event object is and how to use it in a multithreaded program.

Download the example here.

Theory:

From the previous tutorial, I demonstrated how threads communicate with a custom window message. I left out two other methods: global variable and event object. We will use both of them in this tutorial.
An event object is like a switch: it has only two states: on or off. When an event object is turned on, it's in the "signalled" state. When it is turned off, it's in the "nonsignalled" state. You create an event object and put in a code snippet in the relevant threads to watch for the state of the event object. If the event object is in the nonsignalled state, the threads that wait for it will be asleep.When the threads are in wait state, they consume little CPU time.
You create an event object by calling CreateEvent function which has the following syntax:

CreateEvent proto lpEventAttributes:DWORD,\
bManualReset:DWORD,\
bInitialState:DWORD,\
lpName:DWORD

lpEventAttribute--> If you specify NULL value, the event object is created with default security descriptor.
bManualReset--> If you want Windows to automatically reset the event object to nonsignalled state after WaitForSingleObject call, you must specify FALSE as this parameter. Else you must manually reset the event object with the call to ResetEvent.
bInitialState--> If you want the event object to be created in the signalled state, specify TRUE as this parameter else the event object will be created in the nonsignalled state.
lpName --> Pointer to an ASCIIZ string that is the name of the event object. This name is used when you want to call OpenEvent.

If the call is successful, it returns the handle to the newly created event object else it returns NULL.
You can modify the state of an event object with two API calls: SetEvent and ResetEvent. SetEvent function sets the event object into signalled state. ResetEvent does the reverse.
When the event object is created, you must put the call to WaitForSingleObject in the thread that wants to watch for the state of the event object. WaitForSingleObject has the following syntax:

WaitForSingleObject proto hObject:DWORD, dwTimeout:DWORD

hObject --> A handle to one of the synchronization object. Event object is a type of synchronization object.
dwTimeout --> specify the time in milliseconds that this function will wait for the object to be in signalled state. If the specified time has passed and the event object is still in nonsignalled state, WaitForSingleObject returns the the caller. If you want to wait for the object indefinitely, you must specify the value INFINITE as this parameter.

Example:

The example below displays a window waiting for the user to select a command from the menu. If the user selects "run thread", the thread starts the savage calculation. When it's finished, a message box appears informing the user that the job is done. During the time that the thread is running, the user can select "stop thread" to stop the thread.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.const
IDM_START_THREAD equ 1
IDM_STOP_THREAD equ 2
IDM_EXIT equ 3
WM_FINISH equ WM_USER+100h

.data
ClassName db "Win32ASMEventClass",0
AppName db "Win32 ASM Event Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0
StopString db "The thread is stopped",0
EventStop BOOL FALSE

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
hMenu HANDLE ?
ThreadID DWORD ?
ExitCode DWORD ?
hEventStart HANDLE ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke CreateEvent,NULL,FALSE,FALSE,NULL
mov hEventStart,eax
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax
.ELSEIF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_START_THREAD
invoke SetEvent,hEventStart
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_ENABLED
.elseif ax==IDM_STOP_THREAD
mov EventStop,TRUE
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSEIF uMsg==WM_FINISH
invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

ThreadProc PROC USES ecx Param:DWORD
invoke WaitForSingleObject,hEventStart,INFINITE
mov ecx,600000000
.WHILE ecx!=0
.if EventStop!=TRUE
add eax,eax
dec ecx
.else
invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
mov EventStop,FALSE
jmp ThreadProc
.endif
.ENDW
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED
jmp ThreadProc
ret
ThreadProc ENDP
end start

Analysis:

In this example, I demonstrate another thread technique.

.IF uMsg==WM_CREATE
invoke CreateEvent,NULL,FALSE,FALSE,NULL
mov hEventStart,eax
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax

You can see that I create the event object and the thread during the processing of WM_CREATE message. I create the event object in the nonsignalled state with automatic reset. After the event object is created, I create the thread. However the thread doesn't run immediately because it waits for the event object to be in the signalled state as the code below:

ThreadProc PROC USES ecx Param:DWORD
invoke WaitForSingleObject,hEventStart,INFINITE
mov ecx,600000000

The first line of the thread procedure is the call to WaitForSingleObject. It waits infinitely for the signalled state of the event object before it returns. This means that even when the thread is created, we put it into a dormant state.
When the user selects "run thread" command from the menu, we set the event object into signalled state as below:

.if ax==IDM_START_THREAD
invoke SetEvent,hEventStart

The call to SetEvent turns the event object into the signalled state which in turn makes the WaitForSingleObject call in the thread procedure return and the thread starts running. When the user selects "stop thread" command, we set the value of the global variable "EventStop" to TRUE.

.if EventStop==FALSE
add eax,eax
dec ecx
.else
invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
mov EventStop,FALSE
jmp ThreadProc
.endif

This stops the thread and jumps to the WaitForSingleObject call again. Note that we don't have to manually reset the event object into nonsignalled state because we specify the bManualReset parameter of the CreateEvent call as FALSE.

[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32asm tutorial 17: Dynamic Link Libraries

Tutorial 17: Dynamic Link Libraries

In this tutorial, we will learn about DLLs , what are they and how to create them.
You can download the example here.

Theory:

If you program long enough, you'll find that the programs you wrote usually have some code routines in common. It's such a waste of time to rewrite them everytime you start coding new programs. Back in the old days of DOS, programmers store those commonly used routines in one or more libraries. When they want to use the functions, they just link the library to the object file and the linker extracts the functions from the library and inserts them into the final executable file. This process is called static linking. C runtime libraries are good examples. The drawback of this method is that you have identical functions in every program that calls them. Your disk space is wasted storing several identical copies of the functions. But for DOS programs, this method is quite acceptable since there is usually only one program that's active in memory. So there is no waste of precious memory.
Under Windows, the situation becomes much more critical because you can have several programs running simultaneously. Memory will be eat up quickly if your program is quite large. Windows has a solution for this type of problem: dynamic link libraries. A dynamic link library is a kind of common pool of functions. Windows will not load several copies of a DLL into memory so even if there are many instances of your program running at the same time, there'll be only one copy of the DLL that program uses in memory. And I should clarify this point a bit. In reality, all processes that use the same dll will have their own copies of that dll. It will look like there are many copies of the DLL in memory. But in reality, Windows does it magic with paging and all processes share the same DLL code.So in physical memory, there is only one copy of DLL code. However, each process will have its own unique data section of the DLL.
The program links to a DLL at runtime unlike the old static library. That's why it's called dynamic link library. You can also unload a DLL at runtime as well when you don't need it. If that program is the only one that uses the DLL, it'll be unloaded from memory immediately. But if the DLL is still used by some other program, the DLL remains in memory until the last program that uses its service unloads it.
However, the linker has a more difficult job when it performs address fixups for the final executable file. Since it cannot "extract" the functions and insert them into the final executable file, somehow it must store enough information about the DLL and functions into the final execuable file for it to be able to locate and load the correct DLL at runtime.
That's where import library comes in. An import library contains the information about the DLL it represents. The linker can extract the info it needs from the import libraries and stuff it into the executable file. When Windows loader loads the program into memory, it sees that the program links to a DLL so it searches for that DLL and maps it into the address space of the process as well and performs the address fixups for the calls to the functions in the DLL.
You may choose to load the DLL yourself without relying on Windows loader. This method has its pros and cons:
  • It doesn't need an import library so you can load and use any DLL even if it comes with no import library. However, you still have to know about the functions inside it, how many parameters they take and the likes.
  • When you let the loader load the DLL for your program, if the loader cannot find the DLL it will report "A required .DLL file, xxxxx.dll is missing" and poof! your program doesn't have a chance to run even if that DLL is not essential to its operation. If you load the DLL yourself, when the DLL cannot be found and it's not essential to the operation, your program can just tell the user about the fact and go on.
  • You can call *undocumented* functions that are not included in the import libraries. Provided that you know enough info about the functions.
  • If you use LoadLibrary, you have to call GetProcAddress for every function that you want to call. GetProcAddress retrieves the entrypoint address of a function in a particular DLL. So your code might be a little bit larger and slower but by not much.
Seeing the advantages/disadvantages of LoadLibrary call, we go into detail how to create a DLL now.
The following code is the DLL skeleton.

;--------------------------------------------------------------------------------------
; DLLSkeleton.asm
;--------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp
;---------------------------------------------------------------------------------------------------
; This is a dummy function
; It does nothing. I put it here to show where you can insert functions into
; a DLL.
;----------------------------------------------------------------------------------------------------
TestFunction proc
ret
TestFunction endp

End DllEntry

;-------------------------------------------------------------------------------------
; DLLSkeleton.def
;-------------------------------------------------------------------------------------
LIBRARY DLLSkeleton
EXPORTS TestFunction

The above program is the DLL skeleton. Every DLL must have an entrypoint function. Windows will call the entrypoint function everytime that:

  • The DLL is first loaded
  • The DLL is unloaded
  • A thread is created in the same process
  • A thread is destroyed in the same process
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp

You can name the entrypoint function anything you wish so long as you have a matching END <Entrypoint function name>. This function takes three parameters, only the first two of which are important.
hInstDLL is the module handle of the DLL. It's not the same as the instance handle of the process. You should keep this value if you need to use it later. You can't obtain it again easily.
reason can be one of the four values:

  • DLL_PROCESS_ATTACH The DLL receives this value when it is first injected into the process address space. You can use this opportunity to do initialization.
  • DLL_PROCESS_DETACH The DLL receives this value when it is being unloaded from the process address space. You can use this opportunity to do some cleanup such as deallocate memory and so on.
  • DLL_THREAD_ATTACH The DLL receives this value when the process creates a new thread.
  • DLL_THREAD_DETACH The DLL receives this value when a thread in the process is destroyed.
You return TRUE in eax if you want the DLL to go on running. If you return FALSE, the DLL will not be loaded. For example, if your initialization code must allocate some memory and it cannot do that successfully, the entrypoint function should return FALSE to indicate that the DLL cannot run.
You can put your functions in the DLL following the entrypoint function or before it. But if you want them to be callable from other programs, you must put their names in the export list in the module definition file (.def).
A DLL needs a module definition file in its developmental stage. We will take a look at it now.

LIBRARY DLLSkeleton
EXPORTS TestFunction

Normally you must have the first line.The LIBRARY statement defines the internal module name of the DLL. You should match it with the filename of the DLL.
The EXPORTS statement tells the linker which functions in the DLL are exported, that is, callable from other programs. In the example, we want other modules to be able to call TestFunction, so we put its name in the EXPORTS statement.
Another change is in the linker switch. You must put /DLL switch and /DEF:<your def filename> in your linker switches like this:

link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:\masm32\lib DLLSkeleton.obj

The assembler switches are the same, namely /c /coff /Cp. So after you link the object file, you will get .dll and .lib. The .lib is the import library which you can use to link to other programs that use the functions in the DLL.
Next I'll show you how to use LoadLibrary to load a DLL.

;---------------------------------------------------------------------------------------------
; UseDLL.asm
;----------------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

.data
LibName db "DLLSkeleton.dll",0
FunctionName db "TestHello",0
DllNotFound db "Cannot load library",0
AppName db "Load Library",0
FunctionNotFound db "TestHello function not found",0

.data?
hLib dd ? ; the handle of the library (DLL)
TestHelloAddr dd ? ; the address of the TestHello function

.code
start:
invoke LoadLibrary,addr LibName
;---------------------------------------------------------------------------------------------------------
; Call LoadLibrary with the name of the desired DLL. If the call is successful
; it will return the handle to the library (DLL). If not, it will return NULL
; You can pass the library handle to GetProcAddress or any function that requires
; a library handle as a parameter.
;------------------------------------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK
.else
mov hLib,eax
invoke GetProcAddress,hLib,addr FunctionName
;-------------------------------------------------------------------------------------------------------------
; When you get the library handle, you pass it to GetProcAddress with the address
; of the name of the function in that DLL you want to call. It returns the address
; of the function if successful. Otherwise, it returns NULL
; Addresses of functions don't change unless you unload and reload the library.
; So you can put them in global variables for future use.
;-------------------------------------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK
.else
mov TestHelloAddr,eax
call [TestHelloAddr]
;-------------------------------------------------------------------------------------------------------------
; Next, you can call the function with a simple call with the variable containing
; the address of the function as the operand.
;-------------------------------------------------------------------------------------------------------------
.endif
invoke FreeLibrary,hLib
;-------------------------------------------------------------------------------------------------------------
; When you don't need the library anymore, unload it with FreeLibrary.
;-------------------------------------------------------------------------------------------------------------
.endif
invoke ExitProcess,NULL
end start

So you can see that using LoadLibrary is a little more involved but it's also more flexible.

[Iczelion's Win32 Assembly Homepage]


Iczelion Win32asm Tutorial 18: Common Controls

Tutorial 18: Common Controls

We will learn what common controls are and how to use them. This tutorial will be a quick introduction to them only.
Download the example source code here.

Theory:

Windows 95 comes with several user-interface enhancements over Windows 3.1x. They make the GUI richer. Several of them are in widely used before Windows 95 hit the shelf, such as status bar, toolbars etc. Programmers have to code them themselves. Now Microsoft has included them with Windows 9x and NT. We will learn about them here.
These are the new controls:
  • Toolbar
  • Tooltip
  • Status bar
  • Property sheet
  • Property page
  • Tree view
  • List view
  • Animation
  • Drag list
  • Header
  • Hot-key
  • Image list
  • Progress bar
  • Right edit
  • Tab
  • Trackbar
  • Up-down
Since there are many of them, loading them all into memory and registering them would be a waste of resource. All of them, with the exception of rich edit control, are stored in comctl32.dll with applications can load when they want to use the controls. Rich edit control resides in its own dll, richedXX.dll, because it's very complicated and hence larger than its brethren.
You can load comctl32.dll by including a call to InitCommonControls in your program. InitCommonControls is a function in comctl32.dll, so referring to it anywhere in your code will make PE loader load comctl32.dll when your program runs.You don't have to execute it, just include it in your code somewhere. This function does NOTHING! Its only instruction is "ret". Its sole purpose is to include reference to comctl32.dll in the import section so that PE loader will load it whenever the program is loaded. The real workhorse is the DLL entrypoint function which registers all common control classes when the dll is loaded. Common controls are created based on those classes just like other child window controls such as edit, listbox etc.
Rich edit is another matter entirely. If you want to use it, you have to call LoadLibrary to load it explicitly and call FreeLibrary to unload it.
Now we learn how to create them. You can use a resource editor to incorporate them into dialog boxes or you can create them yourself. Nearly all common controls are created by calling CreateWindowEx or CreateWindow, passing it the name of the control class. Some common controls have specific creation functions , however, they are just wrappers around CreateWindowEx to make it easier to create those controls. Existing specific creation functions are listed below:
  • CreateToolbarEx
  • CreateStatusWindow
  • CreatePropertySheetPage
  • PropertySheet
  • ImageList_Create
In order to create common controls, you have to know their class names. They are listed below:
Class Name | Common Control
ToolbarWindow32 | Toolbar
tooltips_class32 | Tooltip
msctls_statusbar32 | Status bar
SysTreeView32 | Tree view
SysListView32 | List view
SysAnimate32 | Animation
SysHeader32 | Header
msctls_hotkey32 | Hot-key
msctls_progress32 | Progress bar
RICHEDIT | Rich edit
msctls_updown32 | Up-down
SysTabControl32 | Tab
Property sheets and property pages and image list control have their own specific creation functions. Drag list control are souped-up listbox so it doesn't have its own class. The above class names are verified by checking resource script generated by Visual C++ resource editor. They differ from the class names listed by Borland's win32 api reference and Charles Petzold's Programming Windows 95. The above list is the accurate one.
Those common controls can use general window styles such as WS_CHILD etc. They also have their own specific styles such as TVS_XXXXX for tree view control, LVS_xxxx for list view control, etc. Win32 api reference is your best friend in this regard.
Now that we know how to create common controls, we can move on to communication method between common controls and their parent. Unlike child window controls, common controls don't communicate with the parent via WM_COMMAND. Instead they send WM_NOTIFY messages to the parent window when some interesting events occur with the common controls. The parent can control the children by sending messages to them. There are also many new messages for those new controls. You should consult your win32 api reference for more detail.
Let's examine progress bar and status bar controls in the following example.

Sample code:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.const
IDC_PROGRESS equ 1 ; control IDs
IDC_STATUS equ 2
IDC_TIMER equ 3

.data
ClassName db "CommonControlWinClass",0
AppName db "Common Control Demo",0
ProgressClass db "msctls_progress32",0 ; the class name of the progress bar
Message db "Finished!",0
TimerID dd 0

.data?
hInstance HINSTANCE ?
hwndProgress dd ?
hwndStatus dd ?
CurrentStep dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
invoke InitCommonControls

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\
WS_CHILD+WS_VISIBLE,100,\
200,300,20,hWnd,IDC_PROGRESS,\
hInstance,NULL
mov hwndProgress,eax
mov eax,1000 ; the lParam of PBM_SETRANGE message contains the range
mov CurrentStep,eax
shl eax,16 ; the high range is in the high word
invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax
invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0
invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUS
mov hwndStatus,eax
invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; create a timer
mov TimerID,eax
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.if TimerID!=0
invoke KillTimer,hWnd,TimerID
.endif
.elseif uMsg==WM_TIMER ; when a timer event occurs
invoke SendMessage,hwndProgress,PBM_STEPIT,0,0 ; step up the progress in the progress bar
sub CurrentStep,10
.if CurrentStep==0
invoke KillTimer,hWnd,TimerID
mov TimerID,0
invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message
invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SendMessage,hwndStatus,SB_SETTEXT,0,0
invoke SendMessage,hwndProgress,PBM_SETPOS,0,0
.endif
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
end start

Analysis:

    invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
    invoke ExitProcess,eax
    invoke InitCommonControls
I deliberately put InitCommonControls after ExitProcess to demonstrate that InitCommonControls is just there for putting a reference to comctl32.dll in the import section. As you can see, the common controls work even if InitCommonControls doesn't execute.
    .if uMsg==WM_CREATE
    invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\
    WS_CHILD+WS_VISIBLE,100,\
    200,300,20,hWnd,IDC_PROGRESS,\
    hInstance,NULL
    mov hwndProgress,eax
Here is where we create the common control. Note that this CreateWindowEx call contains hWnd as the parent window handle. It also specifies a control ID for identifying this control. However, since we have the control's window handle, this ID is not used. All child window controls must have WS_CHILD style.
    mov eax,1000
    mov CurrentStep,eax
    shl eax,16
    invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax
    invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0
After the progress bar is created, we can set its range. The default range is from 0 to 100. If you are not satisfied with it, you can specify your own range with PBM_SETRANGE message. lParam of this message contains the range, the maximum range is in the high word and the minimum one is in the low word. You can specify how much a step takes by using PBM_SETSTEP message. The example sets it to 10 which means that when you send a PBM_STEPIT message to the progress bar, the progress indicator will rise by 10. You can also set your own indicator level by sending PBM_SETPOS messages. This message gives you tighter control over the progress bar.
    invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUS
    mov hwndStatus,eax
    invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; create a timer
    mov TimerID,eax
Next, we create a status bar by calling CreateStatusWindow. This call is easy to understand so I'll not comment on it. After the status window is created, we create a timer. In this example, we will update the progress bar at a regular interval of 100 ms so we must create a timer control. Below is the function prototype of SetTimer.
    SetTimer PROTO hWnd:DWORD, TimerID:DWORD, TimeInterval:DWORD, lpTimerProc:DWORD
hWnd : Parent window handle
TimerID : a nonzero timer identifier. You can create your own identifier.
TimerInterval : the timer interval in milliseconds that must pass before the timer calls the timer procedure or sends a WM_TIMER message
lpTimerProc : the address of the timer function that will be called when the time interval expires. If this parameter is NULL, the timer will send WM_TIMER message to the parent window instead.

If this call is successful, it will return the TimerID. If it failed, it returns 0. This is why the timer identifer must be a nonzero value.

    .elseif uMsg==WM_TIMER
    invoke SendMessage,hwndProgress,PBM_STEPIT,0,0
    sub CurrentStep,10
    .if CurrentStep==0
    invoke KillTimer,hWnd,TimerID
    mov TimerID,0
    invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message
    invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
    invoke SendMessage,hwndStatus,SB_SETTEXT,0,0
    invoke SendMessage,hwndProgress,PBM_SETPOS,0,0
    .endif
When the specified time interval expires, the timer sends a WM_TIMER message. You will put your code that will be executed here. In this example, we update the progress bar and then check if the maximum limit has been reached. If it has, we kill the timer and then set the text in the status window with SB_SETTEXT message. A message box is displayed and when the user clicks OK, we clear the text in the status bar and the progress bar.
[Iczelion's Win32 Assembly Homepage]


Iczelion's Win32 Assembly Tutorial 19: Tree View Control

Tutorial 19: Tree View Control

In this tutorial, we will learn how to use tree view control. Moreover, we will also learn how to do drag and drop under tree view control and how to use an image list with it.
Download the example here.

Theory:

A tree view control is a special kind of window that represents objects in hierarchical order. An example of it is the left pane of Windows Explorer. You can use this control to show relationships between objects.
You can create a tree view control by calling CreateWindowEx, passing "SysTreeView32" as the class name or you can incorporate it into a dialog box. Don't forget to put InitCommonControls call in your code.
There are several styles specific to the tree view control. These three are the ones mostly used.
    TVS_HASBUTTONS == Displays plus (+) and minus (-) buttons next to parent items. The user clicks the buttons to expand or collapse a parent item's list of child items. To include buttons with items at the root of the tree view, TVS_LINESATROOT must also be specified.
    TVS_HASLINES == Uses lines to show the hierarchy of items.
    TVS_LINESATROOT == Uses lines to link items at the root of the tree-view control. This value is ignored if TVS_HASLINES is not also specified.
The tree view control, like other common controls, communicates with the parent window via messages. The parent window can send various messages to it and the tree view control can send "notification" messages to its parent window. In this regard, the tree view control is not different that any window.
When something interesting occurs to it, it sends a WM_NOTIFY message to the parent window with accompanying information.
    WM_NOTIFY
    wParam == Control ID, this value is not guaranteed to be unique so we don't use it.
    Instead, we use hwndFrom or IDFrom member of the NMHDR structure
    pointed to by lParam
    lParam == Pointer to NMHDR structure. Some controls may pass a pointer to larger
    structure but it must have a NMHDR structure as its first member.
    That is, when you have lParam, you can be sure that it points to a
    NMHDR structure at least.
Next we will examine NMHDR structure.
    NMHDR struct DWORD
    hwndFrom DWORD ?
    idFrom DWORD ?
    code DWORD ?
    NMHDR ends
hwndFrom is the window handle of the control that sends this WM_NOTIFY message.
idFrom is the control ID of the control that sends this WM_NOTIFY message.
code is the actual message the control wants to send to the parent window.
Tree view notifications are those with TVN_ at the beginning of the name. Tree view messages are those with TVM_, like TVM_CREATEDRAGIMAGE. The tree view control sends TVN_xxxx in the code member of NMHDR. The parent window can send TVM_xxxx to control it.

Adding items to a tree view control

After you create a tree view control, you can add items to it. You can do this by sending TVM_INSERTITEM to it.
    TVM_INSERTITEM
    wParam = 0;
    lParam = pointer to a TV_INSERTSTRUCT;
You should know some terminology at this point about the relationship between items in the tree view control.
An item can be parent, child, or both at the same time. A parent item is the item that has some other subitem(s) associated with it. At the same time, the parent item may be a child of some other item. An item without a parent is called a root item. There can be many root items in a tree view control. Now we examine TV_INSERTSTRUCT structure
    TV_INSERTSTRUCT STRUCT DWORD
    hParent DWORD ?
    hInsertAfter DWORD ?
    ITEMTYPE <>
    TV_INSERTSTRUCT ENDS
hParent = Handle to the parent item. If this member is the TVI_ROOT value or NULL, the item is inserted at the root of the tree-view control.
hInsertAfter = Handle to the item after which the new item is to be inserted or one of the following values:
  • TVI_FIRST ==> Inserts the item at the beginning of the list.
  • TVI_LAST ==> Inserts the item at the end of the list.
  • TVI_SORT ==> Inserts the item into the list in alphabetical order.
    ITEMTYPE UNION
    itemex TVITEMEX <>
    item TVITEM <>
    ITEMTYPE ENDS
We will use only TVITEM here.
    TV_ITEM STRUCT DWORD
    imask DWORD ?
    hItem DWORD ?
    state DWORD ?
    stateMask DWORD ?
    pszText DWORD ?
    cchTextMax DWORD ?
    iImage DWORD ?
    iSelectedImage DWORD ?
    cChildren DWORD ?
    lParam DWORD ?
    TV_ITEM ENDS
This structure is used to send and receive info about a tree view item, depending on messages. For example, with TVM_INSERTITEM, it is used to specify the attribute of the item to be inserted into the tree view control. With TVM_GETITEM, it'll be filled with information about the selected tree view item.
imask is used to specify which member(s) of the TV_ITEM structure is (are) valid. For example, if the value in imask is TVIF_TEXT, it means only the pszText member is valid. You can combine several flags together.
hItem is the handle to the tree view item. Each item has its own handle, like a window handle. If you want to do something with an item, you must select it by its handle.
pszText is the pointer to a null-terminated string that is the label of the tree view item.
cchTextMax is used only when you want to retrieve the label of the tree view item. Because you will supply the pointer to the buffer in pszText, Windows has to know the size of the provided buffer. You have to give the size of the buffer in this member.
iImage and iSelectedImage refers to the index into an image list that contains the images to be shown when the item is not selected and when it's selected. If you recall Windows Explorer left pane, the folder images are specified by these two members.
In order to insert an item into the tree view control, you must at least fill in the hParent, hInsertAfter and you should fill imask and pszText members as well.

Adding images to the tree view control

If you want to put an image to the left of the tree view item's label, you have to create an image list and associate it with the tree view control. You can create an image list by calling ImageList_Create.
    ImageList_Create PROTO cx:DWORD, cy:DWORD, flags:DWORD, \
    cInitial:DWORD, cGrow:DWORD
This function returns the handle to an empty image list if successful.
cx == width of each image in this image list, in pixels.
cy == height of each image in this image list, in pixels. Every image in an image list must be equal to each other in size. If you specify a large bitmap, Windows will use cx and cy to *cut* it into several pieces. So you should prepare your own image as a strip of pictures with identical dimensions.
flags == specify the type of images in this image list whether they are color or monochrome and their color depth. Consult your win32 api reference for more detail
cInitial == The number of images that this image list will initially contain. Windows will use this info to allocate memory for the images.
cGrow == Amount of images by which the image list can grow when the system needs to resize the list to make room for new images. This parameter represents the number of new images that the resized image list can contain.
An image list is not a window! It's only an image deposit for use by other windows.
After an image list is created, you can add images to it by calling ImageList_Add
    ImageList_Add PROTO himl:DWORD, hbmImage:DWORD, hbmMask:DWORD
This function returns -1 if unsuccessful.
himl == the handle of the image list you want to add images to. It is the value returned by a successful call to ImageList_Create
hbmImage == the handle to the bitmap to be added to the image list. You usually store the bitmap in the resource and load it with LoadBitmap call. Note that you don't have to specify the number of images contained in this bitmap because this information is inferred from cx and cy parameters passed to ImageList_Create call.
hbmMask == Handle to the bitmap that contains the mask. If no mask is used with the image list, this parameter is ignored.
Normally, we will add only two images to the image list for use with the tree view control: one that is used when the tree view item is not selected, and the other when the item is selected.
When the image list is ready, you associate it with the tree view control by sending TVM_SETIMAGELIST to the tree view control.
    TVM_SETIMAGELIST
    wParam = type of image list to set. There are two choices:
    • TVSIL_NORMAL Set the normal image list, which contains the selected and unselected images for the tree-view item.
    • TVSIL_STATE Set the state image list, which contains the images for tree-view items that are in a user-defined state.
    lParam = Handle to the image list

Retrieve the info about tree view item

You can retrieve the information about a tree view item by sending TVM_GETITEM message.
    TVM_GETITEM
    wParam = 0
    lParam = pointer to the TV_ITEM structure to be filled with the information
Before you send this message, you must fill imask member with the flag(s) that specifies which member(s) of TV_ITEM you want Windows to fill. And most importantly, you must fill hItem with the handle to the item you want to get information from. And this poses a problem: How can you know the handle of the item you want to retrieve info from? Will you have to store all tree view handles?
The answer is quite simple: you don't have to. You can send TVM_GETNEXTITEM message to the tree view control to retrieve the handle to the tree view item that has the attribute(s) you specified. For example, you can query the handle of the first child item, the root item, the selected item, and so on.
    TVM_GETNEXTITEM
    wParam = flag
    lParam = handle to a tree view item (only necessary for some flag values)
The value in wParam is very important so I present all the flags below:
    • TVGN_CARET Retrieves the currently selected item.
    • TVGN_CHILD Retrieves the first child item of the item specified by the hitem parameter
    • TVGN_DROPHILITE Retrieves the item that is the target of a drag-and-drop operation.
    • TVGN_FIRSTVISIBLE Retrieves the first visible item.
    • TVGN_NEXT Retrieves the next sibling item.
    • TVGN_NEXTVISIBLE Retrieves the next visible item that follows the specified item. The specified item must be visible. Use the TVM_GETITEMRECT message to determine whether an item is visible.
    • TVGN_PARENT Retrieves the parent of the specified item.
    • TVGN_PREVIOUS Retrieves the previous sibling item.
    • TVGN_PREVIOUSVISIBLE Retrieves the first visible item that precedes the specified item. The specified item must be visible. Use the TVM_GETITEMRECT message to determine whether an item is visible.
    • TVGN_ROOT Retrieves the topmost or very first item of the tree-view control.
You can see that, you can retrieve the handle to the tree view item you are interested in from this message. SendMessage returns the handle to the tree view item if successful. You can then fill the returned handle into hItem member of TV_ITEM to be used with TVM_GETITEM message.

Drag and Drop Operation in tree view control

This part is the reason I decided to write this tutorial. When I tried to follow the example in win32 api reference (the win32.hlp from InPrise), I was very frustrated because the vital information is lacking. From trial and error, I finally figured out how to implement drag & drop in a tree view control and I don't want anyone to walk the same path as myself.
Below is the steps in implementing drag & drop operation in a tree view control.
  1. When the user tries to drag an item, the tree view control sends TVN_BEGINDRAG notification to the parent window. You can use this opportunity to create a drag image which is the image that will be used to represent the item while it's being dragged. You can send TVM_CREATEDRAGIMAGE to the tree view control to tell it to create a default drag image from the image that is currently used by the item that will be dragged. The tree view control will create an image list with just one drag image and return the handle to that image list to you.
  2. After the drag image is created, you specify the hotspot of the drag image by calling ImageList_BeginDrag.
      3. ImageList_BeginDrag PROTO himlTrack:DWORD, \
      iTrack:DWORD , \
      dxHotspot:DWORD, \
      dyHotspot:DWORD
      himlTrack is the handle to the image list that contains the drag image.
      iTrack is the index into the image list that specifies the drag image
      dxHotspot specifies the relative distance of the hotspot in horizontal plance in the drag image since this image will be used in place of the mouse cursor, so we need to specify which part of the image is the hotspot.
      dyHotspot specifies the relative distance of the hotspot in the vertical plane.
      Normally, iTrack would be 0 if you tell the tree view control to create the drag image for you. and dxHotspot and dyHotspot can be 0 if you want the left upper corner of the drag image to be the hotspot.
  3. When the drag image is ready to be displayed, we call ImageList_DragEnter to display the drag image in the window.
      4. ImageList_DragEnter PROTO hwndLock:DWORD, x:DWORD, y:DWORD
      hwndLock is the handle of the window that owns the drag image. The drag image will not be able to move outside that window.
      x and y are the x-and y-coordinate of the place where the drag image should be initially displayed. Note that these values are relative to the left upper corner of the window, not the client area.
  4. Now that the drag image is displayed on the window, you will have to support the drag operation in the tree view control. However, there is a little problem here. We have to monitor the drag path with WM_MOUSEMOVE and the drop location with WM_LBUTTONUP messages. However, if the drag image is over some other child windows, the parent window will never receive any mouse message. The solution is to capture the mouse input with SetCapture. Using the call, the mouse messages will be directed to the specified window regardless of where the mouse cursor is.
  5. Within WM_MOUSEMOVE handler, you will update the drag path with ImageList_DragMove call. This function moves the image that is being dragged during a drag-and-drop operation. Furthermore, if you so desire, you can hilite the item that the drag image is over by sending TVM_HITTEST to check if the drag image is over some item. If it is, you can send TVM_SELECTITEM with TVGN_DROPHILITE flag to hilite that item. Note that before sending TVM_SELECTITEM message, you must hide the drag image first else your drag image will leave ugly traces. You can hide the drag image by calling ImageList_DragShowNolock and, after the hilite operation is finished, call ImageList_DragShowNolock again to show the drag image.
  6. When the user releases the left mouse button, you must do several things. If you hilite an item, you must un-hilite it by sending TVM_SELECTITEM with TVGN_DROPHILITE flag again, but this time, lParam MUST be zero. If you don't un-hilite the item, you will get a strange effect: when you select some other item, that item will be enclosed by a rectangle but the hilite will still be on the last hilited item. Next, you must call ImageList_DragLeave followed by ImageList_EndDrag. You must release the mouse by calling ReleaseCapture. If you create an image list, you must destroy it by calling ImageList_Destroy. After that, you can go on with what your program wants to do when the drag& drop operation is completed.

Code sample:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.const
IDB_TREE equ 4006 ; ID of the bitmap resource
.data
ClassName db "TreeViewWinClass",0
AppName db "Tree View Demo",0
TreeViewClass db "SysTreeView32",0
Parent db "Parent Item",0
Child1 db "child1",0
Child2 db "child2",0
DragMode dd FALSE ; a flag to determine if we are in drag mode

.data?
hInstance HINSTANCE ?
hwndTreeView dd ? ; handle of the tree view control
hParent dd ? ; handle of the root tree view item
hImageList dd ? ; handle of the image list used in the tree view control
hDragImageList dd ? ; handle of the image list used to store the drag image

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
invoke InitCommonControls

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,200,400,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc uses edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL tvinsert:TV_INSERTSTRUCT
LOCAL hBitmap:DWORD
LOCAL tvhit:TV_HITTESTINFO
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL,\
WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT,0,\
0,200,400,hWnd,NULL,\
hInstance,NULL ; Create the tree view control
mov hwndTreeView,eax
invoke ImageList_Create,16,16,ILC_COLOR16,2,10 ; create the associated image list
mov hImageList,eax
invoke LoadBitmap,hInstance,IDB_TREE ; load the bitmap from the resource
mov hBitmap,eax
invoke ImageList_Add,hImageList,hBitmap,NULL ; Add the bitmap into the image list
invoke DeleteObject,hBitmap ; always delete the bitmap resource
invoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageList
mov tvinsert.hParent,NULL
mov tvinsert.hInsertAfter,TVI_ROOT
mov tvinsert.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE
mov tvinsert.item.pszText,offset Parent
mov tvinsert.item.iImage,0
mov tvinsert.item.iSelectedImage,1
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
mov hParent,eax
mov tvinsert.hParent,eax
mov tvinsert.hInsertAfter,TVI_LAST
mov tvinsert.item.pszText,offset Child1
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
mov tvinsert.item.pszText,offset Child2
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
.elseif uMsg==WM_MOUSEMOVE
.if DragMode==TRUE
mov eax,lParam
and eax,0ffffh
mov ecx,lParam
shr ecx,16
mov tvhit.pt.x,eax
mov tvhit.pt.y,ecx
invoke ImageList_DragMove,eax,ecx
invoke ImageList_DragShowNolock,FALSE
invoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit
.if eax!=NULL
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax
.endif
invoke ImageList_DragShowNolock,TRUE
.endif
.elseif uMsg==WM_LBUTTONUP
.if DragMode==TRUE
invoke ImageList_DragLeave,hwndTreeView
invoke ImageList_EndDrag
invoke ImageList_Destroy,hDragImageList
invoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eax
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0
invoke ReleaseCapture
mov DragMode,FALSE
.endif
.elseif uMsg==WM_NOTIFY
mov edi,lParam
assume edi:ptr NM_TREEVIEW
.if [edi].hdr.code==TVN_BEGINDRAG
invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0,[edi].itemNew.hItem
mov hDragImageList,eax
invoke ImageList_BeginDrag,hDragImageList,0,0,0
invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.y
invoke SetCapture,hWnd
mov DragMode,TRUE
.endif
assume edi:nothing
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
end start

Analysis:

Within WM_CREATE handler, you create the tree view control
    invoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL,\
    WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT,0,\
    0,200,400,hWnd,NULL,\
    hInstance,NULL
Note the styles. TVS_xxxx are the tree view specific styles.
    invoke ImageList_Create,16,16,ILC_COLOR16,2,10
    mov hImageList,eax
    invoke LoadBitmap,hInstance,IDB_TREE
    mov hBitmap,eax
    invoke ImageList_Add,hImageList,hBitmap,NULL
    invoke DeleteObject,hBitmap
    invoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageList
Next, you create an empty image list with will accept images of 16x16 pixels in size, 16-bit color and initially, it will contain 2 images but can be expanded to 10 if need arises. We then load the bitmap from the resource and add it to the image list just created. After that, we delete the handle to the bitmap since it will not be used anymore. When the image list is all set, we associate it with the tree view control by sending TVM_SETIMAGELIST to the tree view control.
    mov tvinsert.hParent,NULL
    mov tvinsert.hInsertAfter,TVI_ROOT
    mov tvinsert.u.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE
    mov tvinsert.u.item.pszText,offset Parent
    mov tvinsert.u.item.iImage,0
    mov tvinsert.u.item.iSelectedImage,1
    invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
We insert items into the tree view control, beginning from the root item. Since it will be root item, hParent member is NULL and hInsertAfter is TVI_ROOT. imask member specifies that pszText, iImage and iSelectedImage members of the TV_ITEM structure is valid. We fill those three members with appropriate value. pszText contains the label of the root item, iImage is the index into the image in the image list that will be displayed to the left of the unselected item, and iSelectedImage is the index into the image in the image list that will be displayed when the item is selected. When all appropriate members are filled in, we send TVM_INSERTITEM message to the tree view control to add the root item to it.
    mov hParent,eax
    mov tvinsert.hParent,eax
    mov tvinsert.hInsertAfter,TVI_LAST
    mov tvinsert.u.item.pszText,offset Child1
    invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
    mov tvinsert.u.item.pszText,offset Child2
    invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
After the root item is added, we can attach the child items to it. hParent member is now filled with the handle of the parent item. And we will use identical images in the image list so we don't change iImage and iSelectedImage member.
    .elseif uMsg==WM_NOTIFY
    mov edi,lParam
    assume edi:ptr NM_TREEVIEW
    .if [edi].hdr.code==TVN_BEGINDRAG
    invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0,[edi].itemNew.hItem
    mov hDragImageList,eax
    invoke ImageList_BeginDrag,hDragImageList,0,0,0
    invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.y
    invoke SetCapture,hWnd
    mov DragMode,TRUE
    .endif
    assume edi:nothing
Now when the user tries to drag an item, the tree view control sends WM_NOTIFY message with TVN_BEGINDRAG as the code. lParam is the pointer to an NM_TREEVIEW structure which contains several pieces of information we need so we put its value into edi and use edi as the pointer to NM_TREEVIEW structure. assume edi:ptr NM_TREEVIEW is a way to tell MASM to treat edi as the pointer to NM_TREEVIEW structure. We then create a drag image by sending TVM_CREATEDRAGIMAGE to the tree view control. It returns the handle to the newly created image list with a drag image inside. We call ImageList_BeginDrag to set the hotspot in the drag image. Then we enter the drag operation by calling ImageList_DragEnter. This function displays the drag image at the specified location in the specified window. We use ptDrag structure that is a member of NM_TREEVIEW structure as the point where the drag image should be initially displayed.After that, we capture the mouse input and set the flag to indicate that we now enter drag mode.
    .elseif uMsg==WM_MOUSEMOVE
    .if DragMode==TRUE
    mov eax,lParam
    and eax,0ffffh
    mov ecx,lParam
    shr ecx,16
    mov tvhit.pt.x,eax
    mov tvhit.pt.y,ecx
    invoke ImageList_DragMove,eax,ecx
    invoke ImageList_DragShowNolock,FALSE
    invoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit
    .if eax!=NULL
    invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax
    .endif
    invoke ImageList_DragShowNolock,TRUE
    .endif
Now we concentrate on WM_MOUSEMOVE. When the user drags the drag image along, our parent window receives WM_MOUSEMOVE messages. In response to these messages, we update the drag image position with ImageList_DragMove. After that, we check if the drag image is over some item. We do that by sending TVM_HITTEST message to the tree view control with a point for it to check. If the drag image is over some item, we hilite that item by sending TVM_SELECTITEM message with TVGN_DROPHILITE flag to the tree view control. During the hilite operation, we hide the drag image so that it will not leave unsightly blots on the tree view control.
    .elseif uMsg==WM_LBUTTONUP
    .if DragMode==TRUE
    invoke ImageList_DragLeave,hwndTreeView
    invoke ImageList_EndDrag
    invoke ImageList_Destroy,hDragImageList
    invoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0
    invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eax
    invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0
    invoke ReleaseCapture
    mov DragMode,FALSE
    .endif
When the user releases the left mouse button, the drag operation is at the end. We leave the drag mode by calling ImageList_DragLeave, followed by ImageList_EndDrag and ImageList_Destroy. To make the tree view items look good, we also check the last hilited item, and select it. We must also un-hilite it else the other items will not get hilited when they are selected. And lastly, we release the mouse capture.
[Iczelion's Win32 Assembly Homepage]


Iczelion's Win32asm Tutorial 20: Window Subclassing

Tutorial 20: Window Subclassing

In this tutorial, we will learn about window subclassing, what it is and how to use it to your advantage.
Download the example here.

Theory:

If you program in Windows for some time, you will find some cases where a window has nearly the attributes you need in your program but not quite. Have you encountered a situation where you want some special kind of edit control that can filter out some unwanted text? The straightforward thing to do is to code your own window. But it's really hard work and time-consuming. Window subclassing to the rescue.
In a nutshell, window subclassing allows you to "take over" the subclassed window. You will have absolute control over it. Let's take an example to make this clearer. Suppose you need a text box that accepts only hex numbers. If you use a simple edit control, you have no say whatsoever when your user types something other than hex numbers into your text box, ie. if the user types "zb+q*" into your text box, you can't do anything with it except rejecting the whole text string. This is unprofessional at least. In essence, you need the ability to examine each character the user typed into the text box right at the moment he typed it.
We will examine how to do that now. When the user types something into a text box, Windows sends WM_CHAR message to the edit control's window procedure. This window procedure resides inside Windows itself so we can't modify it. But we can redirect the message flow to our own window procedure. So that our window procedure will get first shot at any message Windows sends to the edit control. If our window procedure chooses to act on the message, it can do so. But if it doesn't want to handle the message, it can pass it to the original window procedure. This way, our window procedure inserts itself between Windows and the edit control. Look at the flow below:

    Before Subclassing

    Windows ==> edit control's window procedure

    After Subclassing

    Windows ==> our window procedure -----> edit control's window procedure
Now we put our attention on how to subclass a window. Note that subclassing is not limited to controls, it can be used with any window.
Let's think about how Windows knows where the edit control's window procedure resides. A guess?......lpfnWndProc member of WNDCLASSEX structure. If we can replace this member with the address of our own window procedure, Windows will send messages to our window proc instead.
We can do that by calling SetWindowLong.
    SetWindowLong PROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORD
hWnd = handle of the window to change the value in the WNDCLASSEX structure
nIndex == value to change.
    GWL_EXSTYLE Sets a new extended window style.
    GWL_STYLE Sets a new window style.
    GWL_WNDPROC Sets a new address for the window procedure.
    GWL_HINSTANCE Sets a new application instance handle.
    GWL_ID Sets a new identifier of the window.
    GWL_USERDATA Sets the 32-bit value associated with the window. Each window has a corresponding 32-bit value intended for use by the application that created the window.
dwNewLong = the replacement value.
So our job is easy: We code a window proc that will handle the messages for the edit control and then call SetWindowLong with GWL_WNDPROC flag, passing along the address of our window proc as the third parameter. If the function succeeds, the return value is the previous value of the specified 32-bit integer, in our case, the address of the original window procedure. We need to store this value for use within our window procedure.
Remember that there will be some messages we don't want to handle, we will pass them to the original window procedure. We can do that by calling CallWindowProc function.
    CallWindowProc PROTO lpPrevWndFunc:DWORD, \
    hWnd:DWORD,\
    Msg:DWORD,\
    wParam:DWORD,\
    lParam:DWORD
lpPrevWndFunc = the address of the original window procedure.
The remaining four parameters are the ones passed to our window procedure. We just pass them along to CallWindowProc.

Code Sample:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "SubclassWinClass",0
AppName db "Subclassing Demo",0
EditClass db "EDIT",0
Message db "You pressed Enter in the text box!",0

.data?
hInstance HINSTANCE ?
hwndEdit dd ?
OldWndProc dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR EditClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
20,300,25,hWnd,NULL,\
hInstance,NULL
mov hwndEdit,eax
invoke SetFocus,eax
;-----------------------------------------
; Subclass it!
;-----------------------------------------
invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc
mov OldWndProc,eax
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp

EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_CHAR
mov eax,wParam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
ret
.endif
.elseif uMsg==WM_KEYDOWN
mov eax,wParam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
EditWndProc endp
end start

Analysis:

    invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc
    mov OldWndProc,eax
After the edit control is created, we subclass it by calling SetWindowLong, replacing the address of the original window procedure with our own window procedure. Note that we store the address of the original window procedure for use with CallWindowProc. Note the EditWndProc is an ordinary window procedure.
    .if uMsg==WM_CHAR
    mov eax,wParam
    .if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
    .if al>="a" && al<="f"
    sub al,20h
    .endif
    invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
    ret
    .endif
Within EditWndProc, we filter WM_CHAR messages. If the character is between 0-9 or a-f, we accept it by passing along the message to the original window procedure. If it is a lower case character, we convert it to upper case by adding it with 20h. Note that, if the character is not the one we expect, we discard it. We don't pass it to the original window proc. So when the user types something other than 0-9 or a-f, the character just doesn't appear in the edit control.
    .elseif uMsg==WM_KEYDOWN
    mov eax,wParam
    .if al==VK_RETURN
    invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
    invoke SetFocus,hEdit
    .else
    invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
    ret
    .end
I want to demonstrate the power of subclassing further by trapping Enter key. EditWndProc checks WM_KEYDOWN message if it's VK_RETURN (the Enter key). If it is, it displays a message box saying "You pressed the Enter key in the text box!". If it's not an Enter key, it passes the message to the original window procedure.
You can use window subclassing to take control over other windows. It's one of the powerful techniques you should have in your arsenal.
[Iczelion's Win32 Assembly Homepage]


Iczelion's Win32 Assembly Tutorial 21: Pipe

Tutorial 21: Pipe

In this tutorial, we will explore pipe, what it is and what we can use it for. To make it more interesting, I throw in the technique on how to change the background and text color of an edit control.
Download the example here.

Theory:

Pipe is a communication conduit or pathway with two ends. You can use pipe to exchange the data between two different processes, or within the same process. It's like a walkie-talkie. You give the other party one set and he can use it to communicate with you.
There are two types of pipes: anonymous and named pipes. Anonymous pipe is, well, anonymous: that is, you can use it without knowing its name. A named pipe is the opposite: you have to know its name before you can use it.
You can also categorize pipes according to its property: one-way or two-way. In a one-way pipe, the data can flow only in one direction: from one end to the other. While in a two-way pipe, the data can be exchanged between both ends.
An anonymous pipe is always one-way while a named pipe can be one-way or two-way. A named pipe is usually used in a network environment where a server can connect to several clients.
In this tutorial, we will examine anonymous pipe in some detail. Anonymous pipe's main purpose is to be used as a communcation pathway between a parent and child processes or between child processes.
Anonymous pipe is really useful when you deal with a console application. A console application is a kind of win32 program which uses a console for its input & output. A console is like a DOS box. However, a console application is a fully 32-bit program. It can use any GUI function, the same as other GUI programs. It just happens to have a console for its use.
A console application has three handles it can use for its input & output. They are called standard handles. There are three of them: standard input, standard output and standard error. Standard input handle is used to read/retrieve the information from the console and standard output handle is used to output/print the information to the console. Standard error handle is used to report error condition since its output cannot be redirected.
A console application can retrieve those three standard handles by calling GetStdHandle function, specifying the handle it wants to obtain. A GUI application doesn't have a console. If you call GetStdHandle, it will return error. If you really want to use a console, you can call AllocConsole to allocate a new console. However, don't forget to call FreeConsole when you're done with the console.
Anonymous pipe is most frequently used to redirect input and/or output of a child console application. The parent process may be a console or a GUI application but the child must be a console app. for this to work. As you know, a console application uses standard handles for its input and output. If we want to redirect the input and/or output of a console application, we can replace the handle with a handle to one end of a pipe. A console application will not know that it's using a handle to one end of a pipe. It'll use it as a standard handle. This is a kind of polymorphism, in OOP jargon. This approach is powerful since we need not modify the child process in anyway.
Another thing you should know about a console application is where it gets those standard handles from. When a console application is created, the parent process has two choices: it can create a new console for the child or it can let the child inherit its own console. For the second approach to work, the parent process must be a console application or if it's a GUI application, it must call AllocConsole first to allocate a console.
Let's begin the work. In order to create an anonymous pipe you need to call CreatePipe. CreatePipe has the following prototype:
CreatePipe proto pReadHandle:DWORD, \
pWriteHandle:DWORD,\
pPipeAttributes:DWORD,\
nBufferSize:DWORD
  • pReadHandle is a pointer to a dword variable that will receive the handle to the read end of the pipe
  • pWriteHandle is a pointer to a dword variable that will receive the handle to the write end of the pipe.
  • pPipeAttributes points to a SECURITY_ATTRIBUTES structure that determines whether the returned read & write handles are inheritable by child processes
  • nBufferSize is the suggested size of the buffer the pipe will reserve for use. This is a suggested size only. You can use NULL to tell the function to use the default size.
If the call is successful, the return value is nonzero. If it failed, the return value is zero.
After the call is successful, you will get two handles, one to read end of the pipe and the other to the write end. Now I will outline the steps needed for redirecting the standard output of a child console program to your own process.Note that my method differs from the one in Borland's win32 api reference. The method in win32 api reference assumes the parent process is a console application and thus the child can inherit the standard handles from it. But most of the time, we will need to redirect output from a console application to a GUI one.
  1. Create an anonymous pipe with CreatePipe. Don't forget to set the bInheritable member of SECURITY_ATTRIBUTES to TRUE so the handles are inheritable.
  2. Now we must prepare the parameters we will pass to CreateProcess since we will use it to load the child console application. One important structure is the STARTUPINFO structure. This structure determines the appearance of the main window of the child process when it first appears. This structure is vital to our purpose. You can hide the main window and pass the pipe handle to the child console process with it. Below is the members you must fill:
    • cb : the size of STARTUPINFO structure
    • dwFlags : the binary bit flags that determine which members of the structure are valid also it governs the show/hide state of the main window. For our purpose, you should use a combination of STARTF_USESHOWWINDOW and STARTF_USESTDHANDLES
    • hStdOutput and hStdError : the handles you want the child process to use as standard output/error handles. For our purpose, we will pass write handle of the pipe as the standard output and error of the child. So when the child outputs something to the standard output/error, it actually passes the info via the pipe to the parent process.
    • wShowWindow governs the show/hide state of the main window. For our purpose, we don't want the console window of the child to show so we put SW_HIDE into this member.
  3. Call CreateProcess to load the child application. After CreateProcess is successful, the child is still dormant. It is loaded into memory but it doesn't run immediately
  4. Close the write pipe handle. This is necessary. Because the parent process has no use for the write pipe handle, and the pipe won't work if there are more than one write end, we MUST close it before reading the data from the pipe. However, don't close the write handle before calling CreateProcess, your pipe will be broken. You should close it just after CreateProcess returns and before you read data from the read end of the pipe.
  5. Now you can read data from the read end of the pipe with ReadFile. With ReadFile, you kick the child process into running mode. It will start execution and when it writes something to the standard output handle (which is actually the handle to the write end of the pipe), the data are sent through the pipe to the read end. You can think of ReadFile as sucking data from the read end of the pipe. You must call ReadFile repeatedly until it returns 0 which means there are no more data to be read. You can do anything with the data you read from the pipe. In our example, I put them into an edit control.
  6. Close the read pipe handle.

Example:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.const
IDR_MAINMENU equ 101 ; the ID of the main menu
IDM_ASSEMBLE equ 40001

.data
ClassName db "PipeWinClass",0
AppName db "One-way Pipe Example",0 EditClass db "EDIT",0
CreatePipeError db "Error during pipe creation",0
CreateProcessError db "Error during process creation",0
CommandLine db "ml /c /coff /Cp test.asm",0

.data?
hInstance HINSTANCE ?
hwndEdit dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,IDR_MAINMENU
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,400,200,NULL,NULL,\ hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL rect:RECT
LOCAL hRead:DWORD
LOCAL hWrite:DWORD
LOCAL startupinfo:STARTUPINFO
LOCAL pinfo:PROCESS_INFORMATION
LOCAL buffer[1024]:byte
LOCAL bytesRead:DWORD
LOCAL hdc:DWORD
LOCAL sat:SECURITY_ATTRIBUTES
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,addr EditClass, NULL, WS_CHILD+ WS_VISIBLE+ ES_MULTILINE+ ES_AUTOHSCROLL+ ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, NULL, hInstance, NULL
mov hwndEdit,eax
.elseif uMsg==WM_CTLCOLOREDIT
invoke SetTextColor,wParam,Yellow
invoke SetBkColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret
.elseif uMsg==WM_SIZE
mov edx,lParam
mov ecx,edx
shr ecx,16
and edx,0ffffh
invoke MoveWindow,hwndEdit,0,0,edx,ecx,TRUE
.elseif uMsg==WM_COMMAND
.if lParam==0
mov eax,wParam
.if ax==IDM_ASSEMBLE
mov sat.nLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE
invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL
.if eax==NULL
invoke MessageBox, hWnd, addr CreatePipeError, addr AppName, MB_ICONERROR+ MB_OK
.else
mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE
invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo
.if eax==NULL
invoke MessageBox,hWnd,addr CreateProcessError,addr AppName,MB_ICONERROR+MB_OK
.else
invoke CloseHandle,hWrite
.while TRUE
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw
.endif
invoke CloseHandle,hRead
.endif
.endif
.endif
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret
.endif
xor eax,eax
ret
WndProc endp
end start

Analysis:

The example will call ml.exe to assemble a file named test.asm and redirect the output of ml.exe to the edit control in its client area.
When the program is loaded, it registers the window class and creates the main window as usual. The first thing it does during main window creation is to create an edit control which will be used to display the output of ml.exe.
Now the interesting part, we will change the text and background color of the edit control. When an edit control is going to paint its client area, it sends WM_CTLCOLOREDIT message to its parent.
wParam contains the handle to the device context that the edit control will use to write its own client area. We can use this opportunity to modify the characteristics of the HDC.
.elseif uMsg==WM_CTLCOLOREDIT
invoke SetTextColor,wParam,Yellow
invoke SetTextColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret
SetTextColor changes the text color to yellow. SetTextColor changes the background color of the text to black. And lastly, we obtain the handle to the black brush which we return to Windows. With WM_CTLCOLOREDIT message, you must return a handle to a brush which Windows will use to paint the background of the edit control. In our example, I want the background to be black so I return the handle to the black brush to Windows.
Now when the user selects Assemble menuitem, it creates an anonymous pipe.

.if ax==IDM_ASSEMBLE
mov sat.nLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE

Prior to calling CreatePipe, we must fill the SECURITY_ATTRIBUTES structure first. Note that we can use NULL in lpSecurityDescriptor member if we don't care about security. And the bInheritHandle member must be TRUE so that the pipe handles are inheritable to the child process.

invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL

After that, we call CreatePipe which, if successful, will fill hRead and hWrite variables with the handles to read and write ends of the pipe respectively.

mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE

Next we must fill the STARTUPINFO structure. We call GetStartupInfo to fill the STARTUPINFO structure with default values of the parent process. You MUST fill the STARTUPINFO structure with this call if you intend your code to work under both win9x and NT. After GetStartupInfo call returns, you can modify the members that are important. We copy the handle to the write end of the pipe into hStdOutput and hStdError since we want the child process to use it instead of the default standard output/error handles. We also want to hide the console window of the child process, so we put SW_HIDE value into wShowWidow member. And lastly, we must indicate that hStdOutput, hStdError and wShowWindow members are valid and must be used by specifying the flags STARTF_USESHOWWINDOW and STARTF_USESTDHANDLES in dwFlags member.

invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo

We now create the child process with CreateProcess call. Note that the bInheritHandles parameter must be set to TRUE for the pipe handle to work.

invoke CloseHandle,hWrite

After we successfully create the child process, we must close the write end of the pipe. Remember that we passed the write handle to the child process via STARTUPINFO structure. If we don't close the write handle from our end, there will be two write ends. And that the pipe will not work. We must close the write handle after CreateProcess but before we read data from the read end of the pipe.

.while TRUE
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw

Now we are ready to read the data from the standard output of the child process. We will stay in an infinite loop until there are no more data left to read from the pipe. We call RtlZeroMemory to fill the buffer with zeroes then call ReadFile, passing the read handle of the pipe in place of a file handle. Note that we only read a maximum of 1023 bytes since we need the data to be an ASCIIZ string which we can pass on to the edit control.
When ReadFile returns with the data in the buffer, we fill the data into the edit control. However, there is a slight problem here. If we use SetWindowText to put the data into the edit control, the new data will overwrite existing data! We want the data to append to the end of the existing data.
To achieve that goal, we first move the caret to the end of the text in the edit control by sending EM_SETSEL message with wParam==-1. Next, we append the data at that point with EM_REPLACESEL message.

invoke CloseHandle,hRead

When ReadFile returns NULL, we break out of the loop and close the read handle.

Iczelion's Win32 Assembly Homepage


Iczelion's Win32 Assembly Tutorial 22: SuperClassing

Tutorial 22: Superclassing

In this tutorial, we will learn about superclassing, what it is and what it is for. You will also learn how to provide Tab key navigation to the controls in your own window.
Download the example here

Theory:

In your programming career, you will surely encounter a situation where you need several controls with *slightly* different behavior. For example, you may need 10 edit controls which accept only number. There are several ways to achieve that goal:
  • Create your own class and instantiate the controls
  • Create those edit control and then subclass all of them
  • Superclass the edit control
The first method is too tedious. You have to implement every functionality of the edit control yourself. Hardly a task to be taken lightly. The second method is better than the first one but still too much work. It is ok if you subclass only a few controls but it's going to be a nightmare to subclass a dozen or so controls. Superclassing is the technique you should use for this occasion.
Subclassing is the method you use to *take control* of a particular window class. By *taking control*, I mean you can modify the properties of the window class to suit your purpose then then create the bunch of controls.
The steps in superclassing is outlined below:
  • call GetClassInfoEx to obtain the information about the window class you want to superclass. GetClassInfoEx requires a pointer to a WNDCLASSEX structure which will be filled with the information if the call returns successfully.
  • Modify the WNDCLASSEX members that you want. However, there are two members which you MUST modify:
    • hInstance You must put the instance handle of your program into this member.
    • lpszClassName You must provide it with a pointer to a new class name.

      • You need not modify lpfnWndProc member but most of the time, you need to do it. Just remember to save the original lpfnWndProc member if you want to call it with CallWindowProc.
  • Register the modifed WNDCLASSEX structure. You'll have a new window class which has several characteristics of the old window class.
  • Create windows from the new class
Superclassing is better than subclassing if you want to create many controls with the same characteristics.

Example:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WM_SUPERCLASS equ WM_USER+5
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "SuperclassWinClass",0
AppName db "Superclassing Demo",0
EditClass db "EDIT",0
OurClass db "SUPEREDITCLASS",0
Message db "You pressed the Enter key in the text box!",0

.data?
hInstance dd ?
hwndEdit dd 6 dup(?)
OldWndProc dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE+WS_EX_CONTROLPARENT,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,220,NULL,NULL,\
hInst,NULL
mov hwnd,eax

.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc uses ebx edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL wc:WNDCLASSEX
.if uMsg==WM_CREATE
mov wc.cbSize,sizeof WNDCLASSEX
invoke GetClassInfoEx,NULL,addr EditClass,addr wc
push wc.lpfnWndProc
pop OldWndProc
mov wc.lpfnWndProc, OFFSET EditWndProc
push hInstance
pop wc.hInstance
mov wc.lpszClassName,OFFSET OurClass
invoke RegisterClassEx, addr wc
xor ebx,ebx
mov edi,20
.while ebx<6
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
edi,300,25,hWnd,ebx,\
hInstance,NULL
mov dword ptr [hwndEdit+4*ebx],eax
add edi,25
inc ebx
.endw
invoke SetFocus,hwndEdit
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp

EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_CHAR
mov eax,wParam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
ret
.endif
.elseif uMsg==WM_KEYDOWN
mov eax,wParam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.elseif al==VK_TAB
invoke GetKeyState,VK_SHIFT
test eax,80000000
.if ZERO?
invoke GetWindow,hEdit,GW_HWNDNEXT
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDFIRST
.endif
.else
invoke GetWindow,hEdit,GW_HWNDPREV
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDLAST
.endif
.endif
invoke SetFocus,eax
xor eax,eax
ret
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
EditWndProc endp
end start

Analysis:

The program will create a simple window with 6 "modified" edit controls in its client area. The edit controls will accept only hex digits. Actually, I modified the subclassing example to do superclassing. The program starts normally and the interesting part is when the main window is created:

.if uMsg==WM_CREATE
mov wc.cbSize,sizeof WNDCLASSEX
invoke GetClassInfoEx,NULL,addr EditClass,addr wc

We must first fill the WNDCLASSEX structure with the data from the class which we want to superclass, in this case, it's EDIT class. Remember that you must set the cbSize member of the WNDCLASSEX structure before you call GetClassInfoEx else the WNDCLASSEX structure will not be filled properly. After GetClassInfoEx returns, wc is filled with all information we need to create a new window class.

push wc.lpfnWndProc
pop OldWndProc
mov wc.lpfnWndProc, OFFSET EditWndProc
push hInstance
pop wc.hInstance
mov wc.lpszClassName,OFFSET OurClass

Now we must modify some members of wc. The first one is the pointer to the window procedure. Since we need to chain our own window procedure with the original one, we have to save it into a variable so we can call it with CallWindowProc. This technique is identical to subclassing except that you modify the WNDCLASSEX structure directly without having to call SetWindowLong. The next two members must be changed else you will not be able to register your new window class, hInstance and lpsClassName. You must replace original hInstance value with hInstance of your own program. And you must choose a new name for the new class.

invoke RegisterClassEx, addr wc

When all is ready, register the new class. You will get a new class with some characteristics of the old class.

xor ebx,ebx
mov edi,20
.while ebx<6
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
edi,300,25,hWnd,ebx,\
hInstance,NULL
mov dword ptr [hwndEdit+4*ebx],eax
add edi,25
inc ebx
.endw
invoke SetFocus,hwndEdit

Now that we registered the class, we can create windows based on it. In the above snippet, I use ebx as the counter of the number of windows created. edi is used as the y coordinate of the left upper corner of the window. When a window is created, its handle is stored in the array of dwords. When all windows are created, set input focus to the first window.
At this point, you got 6 edit controls which accept only hex digits. The substituted window proc handles the filter. Actually, it's identical to the window proc in subclassing example. As you can see, you don't have to do extra work of subclassing them.

I throw in a code snippet to handle control navigation with tabs to make this example more juicy. Normally, if you put controls on a dialog box, the dialog box manager handles the navigation keys for you so you can tab to go to the next control or shift-tab to go back to the previous control. Alas, such feature is not available if you put your controls on a simple window. You have to subclass them so you can handle the Tab keys yourself. In our example, we need not subclass the controls one by one because we already superclassed them, so we can provide a "central control navigation manager" for them.

.elseif al==VK_TAB
invoke GetKeyState,VK_SHIFT
test eax,80000000
.if ZERO?
invoke GetWindow,hEdit,GW_HWNDNEXT
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDFIRST
.endif
.else
invoke GetWindow,hEdit,GW_HWNDPREV
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDLAST
.endif
.endif
invoke SetFocus,eax
xor eax,eax
ret

The above code snippet is from EditWndClass procedure. It checks if the user press Tab key, if so, it call GetKeyState to check if the SHIFT key is also pressed. GetKeyState returns a value in eax that determines whether the specified key is pressed or not. If the key is pressed, the high bit of eax is set. If not, the high bit is clear. So we test the return value against 80000000h. If the high bit is set, it means the user pressed shift+tab which we must handle separately.
If the user press Tab key alone, we call GetWindow to retrieve the handle of the next control. We use GW_HWNDNEXT flag to tell GetWindow to obtain the handle to the window that is next in line to the current hEdit. If this function returns NULL, we interpret it as no more handle to obtain so the current hEdit is the last control in the line. We will "wrap around" to the first control by calling GetWindow with GW_HWNDFIRST flag. Similar to the Tab case, shift-tab just works in reverse.

[Iczelion's Win32 Assembly Homepage]


Iczelion's Win32 Assembly Tutorial 23: Tray Icon

Tutorial 23: Tray Icon

In this tutorial, we will learn how to put icons into system tray and how to create/use a popup menu.
Download the example here.

Theory:

System tray is the rectangular region in the taskbar where several icons reside. Normally, you'll see at least a digital clock in it. You can also put icons in the system tray too. Below are the steps you have to perform to put an icon into the system tray:
  1. Fill a NOTIFYICONDATA structure which has the following members:
    • cbSize The size of this structure.
    • hwnd Handle of the window that will receive notification when a mouse event occurs over the tray icon.
    • uID A constant that is used as the icon's identifier. You are the one who decides on this value. In case you have more than one tray icons, you will be able to check from what tray icon the mouse notification is from.
    • uFlags Specify which members of this structure are valid
      • NIF_ICON The hIcon member is valid.
      • NIF_MESSAGE The uCallbackMessage member is valid.
      • NIF_TIP The szTip member is valid.
    • uCallbackMessage The custom message that Windows will send to the window specified by the hwnd member when mouse events occur over the tray icon. You create this message yourself.
    • hIcon The handle of the icon you want to put into the system tray
    • szTip A 64-byte array that contains the string that will be used as the tooltip text when the mouse hovers over the tray icon.
  2. Call Shell_NotifyIcon which is defined in shell32.inc. This function has the following prototype:

    3. Shell_NotifyIcon PROTO dwMessage:DWORD ,pnid:DWORD

    dwMessage is the type of message to send to the shell.
    NIM_ADD Adds an icon to the status area.
    NIM_DELETE Deletes an icon from the status area.
    NIM_MODIFY Modifies an icon in the status area.
    pnid is the pointer to a NOTIFYICONDATA structure filled with proper values
    If you want to add an icon to the tray, use NIM_ADD message, if you want to remove the icon, use NIM_DELETE.

That's all there is to it. But most of the time, you're not content in just putting an icon there. You need to be able to respond to the mouse events over the tray icon. You can do this by processing the message you specified in uCallbackMessage member of NOTIFYICONDATA structure. This message has the following values in wParam and lParam (special thanks to s__d for the info):
  • wParam contains the ID of the icon. This is the same value you put into uID member of NOTIFYICONDATA structure.
  • lParam The low word contains the mouse message. For example, if the user right-clicked at the icon, lParam will contain WM_RBUTTONDOWN.
Most tray icon, however, displays a popup menu when the user right-click on it. We can implement this feature by creating a popup menu and then call TrackPopupMenu to display it. The steps are described below:
  1. Create a popup menu by calling CreatePopupMenu. This function creates an empty menu. It returns the menu handle in eax if successful.
  2. Add menu items to it with AppendMenu, InsertMenu or InsertMenuItem.
  3. When you want to display the popup menu where the mouse cursor is, call GetCursorPos to obtain the screen coordinate of the cursor and then call TrackPopupMenu to display the menu. When the user selects a menu item from the popup menu, Windows sends WM_COMMAND message to your window procedure just like normal menu selection.
Note: Beware of two annoying behaviors when you use a popup menu with a tray icon:
  1. When the popup menu is displayed, if you click anywhere outside the menu, the popup menu will not disappear immediately as it should be. This behavior occurs because the window that will receive the notifications from the popup menu MUST be the foreground window. Just call SetForegroundWindow will correct it.
  2. After calling SetForegroundWindow, you will find that the first time the popup menu is displayed, it works ok but on the subsequent times, the popup menu will show up and close immediately. This behavior is "intentional", to quote from MSDN. The task switch to the program that is the owner of the tray icon in the near future is necessary. You can force this task switch by posting any message to the window of the program. Just use PostMessage, not SendMessage!

Example:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\shell32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\shell32.lib

WM_SHELLNOTIFY equ WM_USER+5
IDI_TRAY equ 0
IDM_RESTORE equ 1000
IDM_EXIT equ 1010
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "TrayIconWinClass",0
AppName db "TrayIcon Demo",0
RestoreString db "&Restore",0
ExitString db "E&xit Program",0

.data?
hInstance dd ?
note NOTIFYICONDATA <>
hPopupMenu dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_DBLCLKS
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL pt:POINT
.if uMsg==WM_CREATE
invoke CreatePopupMenu
mov hPopupMenu,eax
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString
.elseif uMsg==WM_DESTROY
invoke DestroyMenu,hPopupMenu
invoke PostQuitMessage,NULL
.elseif uMsg==WM_SIZE
.if wParam==SIZE_MINIMIZED
mov note.cbSize,sizeof NOTIFYICONDATA
push hWnd
pop note.hwnd
mov note.uID,IDI_TRAY
mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP
mov note.uCallbackMessage,WM_SHELLNOTIFY
invoke LoadIcon,NULL,IDI_WINLOGO
mov note.hIcon,eax
invoke lstrcpy,addr note.szTip,addr AppName
invoke ShowWindow,hWnd,SW_HIDE
invoke Shell_NotifyIcon,NIM_ADD,addr note
.endif
.elseif uMsg==WM_COMMAND
.if lParam==0
invoke Shell_NotifyIcon,NIM_DELETE,addr note
mov eax,wParam
.if ax==IDM_RESTORE
invoke ShowWindow,hWnd,SW_RESTORE
.else
invoke DestroyWindow,hWnd
.endif
.endif
.elseif uMsg==WM_SHELLNOTIFY
.if wParam==IDI_TRAY
.if lParam==WM_RBUTTONDOWN
invoke GetCursorPos,addr pt
invoke SetForegroundWindow,hWnd
invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL
invoke PostMessage,hWnd,WM_NULL,0,0
.elseif lParam==WM_LBUTTONDBLCLK
invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0
.endif
.endif
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp

end start

Analysis:

The program will display a simple window. When you press the minimize button, it will hide itself and put an icon into the system tray. When you double-click on the icon, the program will restore itself and remove the icon from the system tray. When you right-click on it, a popup menu is displayed. You can choose to restore the program or exit it.

.if uMsg==WM_CREATE
invoke CreatePopupMenu
mov hPopupMenu,eax
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString

When the main window is created, it creates a popup menu and append two menu items. AppendMenu has the following syntax:

AppendMenu PROTO hMenu:DWORD, uFlags:DWORD, uIDNewItem:DWORD, lpNewItem:DWORD
  • hMenu is the handle of the menu you want to append the item to
  • uFlags tells Windows about the menu item to be appended to the menu whether it is a bitmap or a string or an owner-draw item, enabled, grayed or disable etc. You can get the complete list from win32 api reference. In our example, we use MF_STRING which means the menu item is a string.
  • uIDNewItem is the ID of the menu item. This is a user-defined value that is used to represent the menu item.
  • lpNewItem specifies the content of the menu item, depending on what you specify in uFlags member. Since we specify MF_STRING in uFlags member, lpNewItem must contain the pointer to the string to be displayed in the popup menu.
After the popup menu is created, the main window waits patiently for the user to press minimize button.
When a window is minimized, it receives WM_SIZE message with SIZE_MINIMIZED value in wParam.

.elseif uMsg==WM_SIZE
.if wParam==SIZE_MINIMIZED
mov note.cbSize,sizeof NOTIFYICONDATA
push hWnd
pop note.hwnd
mov note.uID,IDI_TRAY
mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP
mov note.uCallbackMessage,WM_SHELLNOTIFY
invoke LoadIcon,NULL,IDI_WINLOGO
mov note.hIcon,eax
invoke lstrcpy,addr note.szTip,addr AppName
invoke ShowWindow,hWnd,SW_HIDE
invoke Shell_NotifyIcon,NIM_ADD,addr note
.endif

We use this opportunity to fill NOTIFYICONDATA structure. IDI_TRAY is just a constant defined at the beginning of the source code. You can set it to any value you like. It's not important because you have only one tray icon. But if you will put several icons into the system tray, you need unique IDs for each tray icon. We specify all flags in uFlags member because we specify an icon (NIF_ICON), we specify a custom message (NIF_MESSAGE) and we specify the tooltip text (NIF_TIP). WM_SHELLNOTIFY is just a custom message defined as WM_USER+5. The actual value is not important so long as it's unique. I use the winlogo icon as the tray icon here but you can use any icon in your program. Just load it from the resource with LoadIcon and put the returned handle in hIcon member. Lastly, we fill the szTip with the text we want the shell to display when the mouse is over the icon.
We hide the main window to give the illusion of "minimizing-to-tray-icon" appearance.
Next we call Shell_NotifyIcon with NIM_ADD message to add the icon to the system tray.

Now our main window is hidden and the icon is in the system tray. If you move the mouse over it, you will see a tooltip that displays the text we put into szTip member. Next, if you double-click at the icon, the main window will reappear and the tray icon is gone.

.elseif uMsg==WM_SHELLNOTIFY
.if wParam==IDI_TRAY
.if lParam==WM_RBUTTONDOWN
invoke GetCursorPos,addr pt
invoke SetForegroundWindow,hWnd
invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL
invoke PostMessage,hWnd,WM_NULL,0,0
.elseif lParam==WM_LBUTTONDBLCLK
invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0
.endif
.endif

When a mouse event occurs over the tray icon, your window receives WM_SHELLNOTIFY message which is the custom message you specified in uCallbackMessage member. Recall that on receiving this message, wParam contains the tray icon's ID and lParam contains the actual mouse message. In the code above, we check first if this message comes from the tray icon we are interested in. If it does, we check the actual mouse message. Since we are only interested in right mouse click and double-left-click, we process only WM_RBUTTONDOWN and WM_LBUTTONDBLCLK messages.
If the mouse message is WM_RBUTTONDOWN, we call GetCursorPos to obtain the current screen coordinate of the mouse cursor. When the function returns, the POINT structure is filled with the screen coordinate of the mouse cursor. By screen coordinate, I mean the coordinate of the entire screen without regarding to any window boundary. For example, if the screen resolution is 640*480, the right-lower corner of the screen is x==639 and y==479. If you want to convert the screen coordinate to window coordinate, use ScreenToClient function.
However, for our purpose, we want to display the popup menu at the current mouse cursor position with TrackPopupMenu call and it requires screen coordinates, we can use the coordinates filled by GetCursorPos directly.
TrackPopupMenu has the following syntax:

    TrackPopupMenu PROTO hMenu:DWORD, uFlags:DWORD, x:DWORD, y:DWORD, nReserved:DWORD, hWnd:DWORD, prcRect:DWORD
  • hMenu is the handle of the popup menu to be displayed
  • uFlags specifies the options of the function. Like where to position the menu relative to the coordinates specified later and which mouse button will be used to track the menu. In our example, we use TPM_RIGHTALIGN to position the popup menu to the left of the coordinates.
  • x and y specify the location of the menu in screen coordinates.
  • nReserved must be NULL
  • hWnd is the handle of the window that will receive the messages from the menu.
  • prcRect is the rectangle in the screen where it is possible to click without dismissing the menu. Normally we put NULL here so when the user clicks anywhere outside the popup menu, the menu is dismissed.
When the user double-clicks at the tray icon, we send WM_COMMAND message to our own window specifying IDM_RESTORE to emulate the user selects Restore menu item in the popup menu thereby restoring the main window and removing the icon from the system tray. In order to be able to receive double click message, the main window must have CS_DBLCLKS style.

invoke Shell_NotifyIcon,NIM_DELETE,addr note
mov eax,wParam
.if ax==IDM_RESTORE
invoke ShowWindow,hWnd,SW_RESTORE
.else
invoke DestroyWindow,hWnd
.endif

When the user selects Restore menu item, we remove the tray icon by calling Shell_NotifyIcon again, this time we specify NIM_DELETE as the message. Next, we restore the main window to its original state. If the user selects Exit menu item, we also remove the icon from the tray and destroy the main window by calling DestroyWindow.

[Iczelion's Win32 Assembly Homepage]


Iczelion's Win32 Assembly Tutorial 24: Windows Hooks

Tutorial 24: Windows Hooks

We will learn about Windows hooks in this tutorial. Windows hooks are very powerful. With them, you can poke inside other processes and sometimes alter their behaviors.
Download the example here.

Theory:

Windows hooks can be considered one of the most powerful features of Windows. With them, you can trap events that will occur, either in your own process or in other processes. By "hooking", you tell Windows about a function, filter function also called hook procedure, that will be called everytime an event you're interested in occurs. There are two types of them: local and remote hooks.
  • Local hooks trap events that will occur in your own process.
  • Remote hooks trap events that will occur in other process(es). There are two types of remote hooks
    • thread-specific traps events that will occur in a specific thread in other process. In short, you want to observe events in a specific thread in a specific process.
    • system-wide traps all events destined for all threads in all processes in the system.
When you install hooks, remember that they affect system performance. System-wide hooks are the most notorious. Since ALL related events will be routed through your filter function, your system may slow down noticeably. So if you use a system-wide hook, you should use it judiciously and unhook it as soon as you don't need it. Also, you have a higher chance of crashing the other processes since you can meddle with other processes and if something is wrong in your filter function, it can pull the other processes down to oblivion with it. Remember: Power comes with responsibility.
You have to understand how a hook works before you can use it efficiently. When you create a hook, Windows creates a data structure in memory, containing information about the hook, and adds it to a linked list of existing hooks. New hook is added in front of old hooks. When an event occurs, if you install a local hook, the filter function in your process is called so it's rather straightforward. But if it's a remote hook, the system must inject the code for the hook procedure into the address space(s) of the other process(es). And the system can do that only if the function resides in a DLL. Thus , if you want to use a remote hook, your hook procedure must reside in a DLL. There is two exceptions to this rule: journal record and journal playback hooks. The hook procedures for those two hooks must reside in the thread that installs the hooks. The reason why it must be so is that: both hooks deal with the low-level interception of hardware input events. The input events must be recorded/playbacked in the order they appeared. If the code of those two hooks is in a DLL, the input events may scatter among several threads and it is impossible to know the order of them. So the solution: the hook procedure of those two hooks must be in a single thread only i.e. the thread that installs the hooks.
There are 14 types of hooks:
  • WH_CALLWNDPROC called when SendMessage is called
  • WH_CALLWNDPROCRET called when SendMessage returns
  • WH_GETMESSAGE called when GetMessage or PeekMessage is called
  • WH_KEYBOARD called when GetMessage or PeekMessage retrieves WM_KEYUP or WM_KEYDOWN from the message queue
  • WH_MOUSE called when GetMessage or PeekMessage retrieves a mouse message from the message queue
  • WH_HARDWARE called when GetMessage or PeekMessage retrieves some hardware message that is not related to keyboard or mouse.
  • WH_MSGFILTER called when a dialog box, menu or scrollbar is about to process a message. This hook is local. It's specifically for those objects which have their own internal message loops.
  • WH_SYSMSGFILTER same as WH_MSGFILTER but system-wide
  • WH_JOURNALRECORD called when Windows retrieves message from the hardware input queue
  • WH_JOURNALPLAYBACK called when an event is requested from the system's hardware input queue.
  • WH_SHELL called when something interesting about the shell occurs such as when the task bar needs to redraw its button.
  • WH_CBT used specifically for computer-based training (CBT).
  • WH_FOREGROUNDIDLE used internally by Windows. Little use for general applications
  • WH_DEBUG used to debug the hooking procedure
Now that we know some theory, we can move on to how to install/uninstall the hooks.
To install a hook, you call SetWindowsHookEx which has the following syntax:
SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORD
  • HookType is one of the values listed above, e.g., WH_MOUSE, WH_KEYBOARD
  • pHookProc is the address of the hook procedure that will be called to process the messages for the specified hook. If the hook is a remote one, it must reside in a DLL. If not, it must be in your process.
  • hInstance is the instance handle of the DLL in which the hook procedure resides. If the hook is a local one, this value must be NULL
  • ThreadID is the ID of the thread you want to install the hook to spy on. This parameter is the one that determines whether a hook is local or remote. If this parameter is NULL, Windows will interpret the hook as a system-wide remote hook that affects all threads in the system. If you specify the thread ID of a thread in your own process, this hook is a local one. If you specify the thread ID from other process, the hook is a thread-specific remote one. There are two exceptions to this rule: WH_JOURNALRECORD and WH_JOURNALPLAYBACK are always local system-wide hooks that are not required to be in a DLL. And WH_SYSMSGFILTER is always a system-wide remote hook. Actually it is identical to WH_MSGFILTER hook with ThreadID==0.
If the call is successful, it returns the hook handle in eax. If not, NULL is returned. You must save the hook handle for unhooking later.
You can uninstall a hook by callingUnhookWindowsHookEx which accepts only one parameter, the handle of the hook you want to uninstall. If the call succeeds, it returns a non-zero value in eax. Otherwise, it returns NULL.
Now that you know how to install/uninstall hooks, we can examine the hook procedure.
The hook procedure will be called whenever an event that is associated with the type of hook you have installed occurs. For example, if you install WH_MOUSE hook, when a mouse event occurs, your hook procedure will be called. Regardless of the type of hook you installed, the hook procedure always has the following prototype:
    HookProc proto nCode:DWORD, wParam:DWORD, lParam:DWORD
    • nCode specifies the hook code.
    • wParam and lParam contain additional information about the event
HookProc is actually a placeholder for the function name. You can name it anything you like so long as it has the above prototype. The interpretation of nCode, wParam and lParam is dependent on the type of hook you install. So as the return value from the hook procedure. For example:
WH_CALLWNDPROC
  • nCode can be only HC_ACTION which means there is a message sent to a window
  • wParam contains the message being sent, if it's not zero
  • lParam points to a CWPSTRUCT structure
  • return value: not used, return zero
WH_MOUSE
  • nCode can be HC_ACTION or HC_NOREMOVE
  • wParam contains the mouse message
  • lParam points to a MOUSEHOOKSTRUCT structure
  • return value: zero if the message should be processed. 1 if the message should be discarded.
The bottom line is: you must consult your win32 api reference for details about the meanings of the parameters and return value of the hook you want to install.
Now there is a little catch about the hook procedure. Remember that the hooks are chained in a linked list with the most recently installed hook at the head of the list. When an event occurs, Windows will call only the first hook in the chain. It's your hook procedure's responsibility to call the next hook in the chain. You can choose not to call the next hook but you'd better know what you're doing. Most of the time, it's a good practice to call the next procedure so other hooks can have a shot at the event. You can call the next hook by calling CallNextHookEx which has the following prototype:
CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORD
  • hHook is your own hook handle. The function uses this handle to traverse the linked list and search for the hook procedure it should call next.
  • nCode, wParam and lParam you can just pass those three values you receive from Windows to CallNextHookEx.
An important note about remote hooks: the hook procedure must reside in a DLL which will be mapped into other processes. When Windows maps the DLL into other processes, it will not map the data section(s) into the other processes. In short, all processes share a single copy of code but they will have their own private copy of the DLL's data section! This can be a big surprise to the unwary. You may think that when you store a value into a variable in the data section of a DLL, that value will be shared among all processes that load the DLL into their process address space. It's simply not true. In normal situation, this behavior is desirable since it provides the illusion that each process has its own copy of the DLL. But not when Windows hook is concerned. We want the DLL to be identical in all processes, including the data. The solution: you must mark the data section as shared. You can do this by specifying the section(s) attribute in the linker switch. For MASM, you need to use this switch:
/SECTION:<section name>, S
The name of the initialized data section is .data and the uninitialized data is .bss. For example if you want to assemble a DLL which contains a hook procedure and you want the uninitialized data section to be shared amoung processes, you must use the following line:
link /section:.bss,S /DLL /SUBSYSTEM:WINDOWS ..........
S attribute marks the section as shared.

Example:

There are two modules: one is the main program which will do the GUI part and the other is the DLL that will install/uninstall the hook.

;--------------------------------------------- This is the source code of the main program --------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include mousehook.inc
includelib mousehook.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

wsprintfA proto C :DWORD,:DWORD,:VARARG
wsprintf TEXTEQU <wsprintfA>

.const
IDD_MAINDLG equ 101
IDC_CLASSNAME equ 1000
IDC_HANDLE equ 1001
IDC_WNDPROC equ 1002
IDC_HOOK equ 1004
IDC_EXIT equ 1005
WM_MOUSEHOOK equ WM_USER+6

DlgFunc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
HookFlag dd FALSE
HookText db "&Hook",0
UnhookText db "&Unhook",0
template db "%lx",0

.data?
hInstance dd ?
hHook dd ?
.code
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,hInstance,IDD_MAINDLG,NULL,addr DlgFunc,NULL
invoke ExitProcess,NULL

DlgFunc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
LOCAL hLib:DWORD
LOCAL buffer[128]:byte
LOCAL buffer1[128]:byte
LOCAL rect:RECT
.if uMsg==WM_CLOSE
.if HookFlag==TRUE
invoke UninstallHook
.endif
invoke EndDialog,hDlg,NULL
.elseif uMsg==WM_INITDIALOG
invoke GetWindowRect,hDlg,addr rect
invoke SetWindowPos, hDlg, HWND_TOPMOST, rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW
.elseif uMsg==WM_MOUSEHOOK
invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128
invoke wsprintf,addr buffer,addr template,wParam
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128
invoke GetClassName,wParam,addr buffer,128
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128
invoke GetClassLong,wParam,GCL_WNDPROC
invoke wsprintf,addr buffer,addr template,eax
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer
.endif
.elseif uMsg==WM_COMMAND
.if lParam!=0
mov eax,wParam
mov edx,eax
shr edx,16
.if dx==BN_CLICKED
.if ax==IDC_EXIT
invoke SendMessage,hDlg,WM_CLOSE,0,0
.else
.if HookFlag==FALSE
invoke InstallHook,hDlg
.if eax!=NULL
mov HookFlag,TRUE
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
.endif
.else
invoke UninstallHook
invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText
mov HookFlag,FALSE
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL
invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL
invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL
.endif
.endif
.endif
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
DlgFunc endp

end start

;----------------------------------------------------- This is the source code of the DLL --------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib

.const
WM_MOUSEHOOK equ WM_USER+6

.data
hInstance dd 0

.data?
hHook dd ?
hWnd dd ?

.code
DllEntry proc hInst:HINSTANCE, reason:DWORD, reserved1:DWORD
.if reason==DLL_PROCESS_ATTACH
push hInst
pop hInstance
.endif
mov eax,TRUE
ret
DllEntry Endp

MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
invoke CallNextHookEx,hHook,nCode,wParam,lParam
mov edx,lParam
assume edx:PTR MOUSEHOOKSTRUCT
invoke WindowFromPoint,[edx].pt.x,[edx].pt.y
invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0
assume edx:nothing
xor eax,eax
ret
MouseProc endp

InstallHook proc hwnd:DWORD
push hwnd
pop hWnd
invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
mov hHook,eax
ret
InstallHook endp

UninstallHook proc
invoke UnhookWindowsHookEx,hHook
ret
UninstallHook endp

End DllEntry

;---------------------------------------------- This is the makefile of the DLL ----------------------------------------------

NAME=mousehook
$(NAME).dll: $(NAME).obj
Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm\lib $(NAME).obj
$(NAME).obj: $(NAME).asm
ml /c /coff /Cp $(NAME).asm

Analysis:

The example will display a dialog box with three edit controls that will be filled with the class name, window handle and the address of the window procedure associated with the window under the mouse cursor. There are two buttons, Hook and Exit. When you press the Hook button, the program hooks the mouse input and the text on the button changes to Unhook. When you move the mouse cursor over a window, the info about that window will be displayed in the main window of the example. When you press Unhook button, the program removes the mouse hook.
The main program uses a dialog box as its main window. It defines a custom message, WM_MOUSEHOOK which will be used between the main program and the hook DLL. When the main program receives this message, wParam contains the handle of the window that the mouse cursor is on. Of course, this is an arbitrary arrangement. I decide to send the handle in wParam for the sake of simplicity. You can choose your own method of communication between the main program and the hook DLL.

.if HookFlag==FALSE
invoke InstallHook,hDlg
.if eax!=NULL
mov HookFlag,TRUE
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
.endif

The program maintains a flag, HookFlag, to monitor the state of the hook. It's FALSE if the hook is not installed and TRUE if the hook is installed.
When the user presses Hook button, the program checks if the hook is already installed. If it is not, it call InstallHook function in the hook DLL to install it. Note that we pass the handle of the main dialog as the parameter of the function so the hook DLL can send the WM_MOUSEHOOK messages to the right window i.e. our own.
When the program is loaded, the hook DLL is loaded too. Actually, DLLs are loaded immediately after the program is in memory. The DLL entrypoint function is called before the first instruction in the main program is execute even. So when the main program executes the DLL(s) is/are initialized. We put the following code in the DLL entrypoint function of the hook DLL:

.if reason==DLL_PROCESS_ATTACH
push hInst
pop hInstance
.endif

The code just saves the instance handle of the hook DLL itself to a global variable named hInstance for use within the InstallHook function. Since the DLL entrypoint function is called before other functions in the DLL are called , hInstance is always valid. We put hInstance in .data section so that this value is kept on per-process basis. Since when the mouse cursor hovers over a window, the hook DLL is mapped into the process. Imagine that there is already a DLL that occupies the intended load address of the hook DLL, the hook DLL would be remapped to another address. The value of hInstance will be updated to those of the new load address. When the user presses Unhook button and then Hook button, SetWindowsHookEx will be called again. However, this time, it will use the new load address as the instance handle which will be wrong because in the example process, the hook DLL's load address hasn't been changed. The hook will be a local one where you can hook only the mouse events that occur in your own window. Hardly desirable.

InstallHook proc hwnd:DWORD
push hwnd
pop hWnd
invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
mov hHook,eax
ret
InstallHook endp

The InstallHook function itself is very simple. It saves the window handle passed as its parameter to a global variable named hWnd for future use. It then calls SetWindowsHookEx to install a mouse hook. The return value of SetWindowsHookEx is stored in a global variable named hHook for use with UnhookWindowsHookEx.
After SetWindowsHookEx is called, the mouse hook is functional. Whenever a mouse event occurs in the system, MouseProc ( your hook procedure) is called.

MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
invoke CallNextHookEx,hHook,nCode,wParam,lParam
mov edx,lParam
assume edx:PTR MOUSEHOOKSTRUCT
invoke WindowFromPoint,[edx].pt.x,[edx].pt.y
invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0
assume edx:nothing
xor eax,eax
ret
MouseProc endp

The first thing it does is to call CallNextHookEx to give the other hooks the chance to process the mouse event. After that, it calls WindowFromPoint function to retrieve the handle of the window at the specified screen coordinate. Note that we use the POINT structure in the MOUSEHOOKSTRUCT structure pointed to by lParam as the current mouse coordinate. After that we send the window handle to the main program via PostMessage with WM_MOUSEHOOK message. One thing you should remember is that: you should not use SendMessage inside the hook procedure, it can cause message deadlock. PostMessage is recommended. The MOUSEHOOKSTRUCT structure is defined below:

MOUSEHOOKSTRUCT STRUCT DWORD
pt POINT <>
hwnd DWORD ?
wHitTestCode DWORD ?
dwExtraInfo DWORD ?
MOUSEHOOKSTRUCT ENDS

  • pt is the current screen coordinate of the mouse cursor
  • hwnd is the handle of the window that will receive the mouse message. It's usually the window under the mouse cursor but not always. If a window calls SetCapture, the mouse input will be redirected to that window instead. Because of this reason, I don't use the hwnd member of this structure but choose to call WindowFromPoint instead.
  • wHitTestCode specifies the hit-test value. The hit-test value gives more information about the current mouse cursor position. It specifies on what part of window the mouse cursor is. For complete list, check your win32 api reference under WM_NCHITTEST message.
  • dwExtraInfo contains the extra information associated with the message. Normally this value is set by calling mouse_event and retrieved by calling GetMessageExtraInfo.
When the main window receives WM_MOUSEHOOK message, it uses the window handle in wParam to retrieve the information about the window.

.elseif uMsg==WM_MOUSEHOOK
invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128
invoke wsprintf,addr buffer,addr template,wParam
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128
invoke GetClassName,wParam,addr buffer,128
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128
invoke GetClassLong,wParam,GCL_WNDPROC
invoke wsprintf,addr buffer,addr template,eax
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer
.endif

To avoid flickers, we check the text already in the edit controls and the text we will put into them if they are identical. If they are, we skip them.
We retrieve the class name by calling GetClassName, the address of the window procedure by calling GetClassLong with GCL_WNDPROC and then format them into strings and put them into the appropriate edit controls.

invoke UninstallHook
invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText
mov HookFlag,FALSE
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL
invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL
invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL

When the user presses Unhook button, the program calls UninstallHook function in the hook DLL. UninstallHook just calls UnhookWindowsHookEx. After that, it changes the text of the button back to "Hook", HookFlag to FALSE and clears the content of the edit controls.
Note the linker switch in the makefile.

Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS

It specifies .bss section as a shared section to make all processes share the same uninitialized data section of the hook DLL. Without this switch, your hook DLL will not function correctly.

[Iczelion's Win32 Assembly Homepage]


Iczelion's Win32asm Tutorial 25: Simple Bitmap

Tutorial 25: Simple Bitmap

In this tutorial, we will learn how to use bitmap in our program. To be exact, we will learn how to display a bitmap in the client area of our window. Download the example.

Theory

Bitmaps can be thought of as pictures stored in computer. There are many picture formats used with computers but Windows only natively supports Windows Bitmap Graphics files (.bmp). The bitmaps I'll refer to in this tutorial are Windows bitmap graphics files. The easiest way to use a bitmap is to use it as a resource. There are two ways to do that. You can include the bitmap in the resource definition file (.rc) as follows:
#define IDB_MYBITMAP 100
IDB_MYBITMAP BITMAP "c:\project\example.bmp"
This method uses a constant to represent the bitmap. The first line just creates a constant named IDB_MYBITMAP which has the value of 100. We will use this label to refer to the bitmap in the program. The next line declares a bitmap resource. It tells the resource compiler where to find the actual bmp file.
The other method uses a name to represent the bitmap as follows:
MyBitMap BITMAP "c:\project\example.bmp"
This method requires that you refer to the bitmap in your program by the string "MyBitMap" instead of a value.
Either method works fine as long as you know which method you're using.
Now that we put the bitmap in the resource file, we can go on with the steps in displaying it in the client area of our window.
  1. call LoadBitmap to get the bitmap handle. LoadBitmap has the following definition:
      LoadBitmap proto hInstance:HINSTANCE, lpBitmapName:LPSTR

    This function returns a bitmap handle. hInstance is the instance handle of our program. lpBitmapName is a pointer to the string that is the name of the bitmap (incase you use the second method to refer to the bitmap). If you use a constant to refer to the bitmap (like IDB_MYBITMAP), you can put its value here. (In the example above it would be 100). A short example is in order:

      First Method:

      .386
      .model flat, stdcall
      ................
      .const
      IDB_MYBITMAP equ 100
      ...............
      .data?
      hInstance dd ?
      ..............
      .code
      .............
      invoke GetModuleHandle,NULL
      mov hInstance,eax
      ............
      invoke LoadBitmap,hInstance,IDB_MYBITMAP
      ...........

      Second Method:

      .386
      .model flat, stdcall
      ................
      .data
      BitmapName db "MyBitMap",0
      ...............
      .data?
      hInstance dd ?
      ..............
      .code
      .............
      invoke GetModuleHandle,NULL
      mov hInstance,eax
      ............
      invoke LoadBitmap,hInstance,addr BitmapName
      ...........

  1. Obtain a handle to device context (DC). You can obtain this handle by calling BeginPaint in response to WM_PAINT message or by calling GetDC anywhere.
  2. Create a memory device context which has the same attribute as the device context we just obtained. The idea here is to create a kind of "hidden" drawing surface which we can draw the bitmap on. When we are finished with the operation, we just copy the content of the hidden drawing surface to the actual device context in one function call. It's an example of double-buffer technique used for fast display of pictures on the screen. You can create this "hidden" drawing surface by calling CreateCompatibleDC.
      CreateCompatibleDC proto hdc:HDC

    If this function succeeds, it returns the handle of the memory device context in eax. hdc is the handle to the device context that you want the memory DC to be compatible with.

  1. Now that you got a hidden drawing surface, you can draw on it by selecting the bitmap into it. This is done by calling SelectObject with the handle to the memory DC as the first parameter and the bitmap handle as the second parameter. SelectObject has the following definition:
      SelectObject proto hdc:HDC, hGdiObject:DWORD
  1. The bitmap is drawn on the memory device context now. All we need to do here is to copy it to the actual display device, namely the true device context. There are several functions that can perform this operation such as BitBlt and StretchBlt. BitBlt just copies the content of one DC to another so it's fast while StretchBlt can stretch or compress the bitmap to fit the output area. We will use BitBlt here for simplicity. BitBlt has the following definition:
      BitBlt proto hdcDest:DWORD, nxDest:DWORD, nyDest:DWORD, nWidth:DWORD, nHeight:DWORD, hdcSrc:DWORD, nxSrc:DWORD, nySrc:DWORD, dwROP:DWORD
    hdcDest is the handle of the device context that serves as the destination of bitmap transfer operation
    nxDest, nyDest are the coordinate of the upper left corner of the output area
    nWidth, nHeight are the width and height of the output area
    hdcSrc is the handle of the device context that serves as the source of bitmap transfer operation
    nxSrc, nySrc are the coordinate of the upper left corner of the source rectangle.
    dwROP is the raster-operation code (hence the acronym ROP) that governs how to combine the color data of the bitmap to the existing color data on the output area to achieve the final result. Most of the time, you only want to overwrite the existing color data with the new one.
  1. When you're done with the bitmap, delete it with DeleteObject API call.
That's it! To recapitulate, you need to put the bitmap into the resource scipt. Then load it from the resource with LoadBitmap. You'll get the bitmap handle. Next you obtain the handle to the device context of the area you want to paint the bitmap on. Then you create a memory device context that is compatible with the device context you just obtained. Select the bitmap into the memory DC then copy the content of the memory DC to the real DC.

Example Code:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
IDB_MAIN   equ 1

.data
ClassName db "SimpleWin32ASMBitmapClass",0
AppName  db "Win32ASM Simple Bitmap Example",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hBitmap dd ?

.code
start:
 invoke GetModuleHandle, NULL
 mov    hInstance,eax
 invoke GetCommandLine
 mov    CommandLine,eax
 invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
 invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
 LOCAL wc:WNDCLASSEX
 LOCAL msg:MSG
 LOCAL hwnd:HWND
 mov   wc.cbSize,SIZEOF WNDCLASSEX
 mov   wc.style, CS_HREDRAW or CS_VREDRAW
 mov   wc.lpfnWndProc, OFFSET WndProc
 mov   wc.cbClsExtra,NULL
 mov   wc.cbWndExtra,NULL
 push  hInstance
 pop   wc.hInstance
 mov   wc.hbrBackground,COLOR_WINDOW+1
 mov   wc.lpszMenuName,NULL
 mov   wc.lpszClassName,OFFSET ClassName
 invoke LoadIcon,NULL,IDI_APPLICATION
 mov   wc.hIcon,eax
 mov   wc.hIconSm,eax
 invoke LoadCursor,NULL,IDC_ARROW
 mov   wc.hCursor,eax
 invoke RegisterClassEx, addr wc
 INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
           hInst,NULL
 mov   hwnd,eax
 invoke ShowWindow, hwnd,SW_SHOWNORMAL
 invoke UpdateWindow, hwnd
 .while TRUE
  invoke GetMessage, ADDR msg,NULL,0,0
  .break .if (!eax)
  invoke TranslateMessage, ADDR msg
  invoke DispatchMessage, ADDR msg
 .endw
 mov     eax,msg.wParam
 ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
   LOCAL ps:PAINTSTRUCT
   LOCAL hdc:HDC
   LOCAL hMemDC:HDC
   LOCAL rect:RECT
   .if uMsg==WM_CREATE
      invoke LoadBitmap,hInstance,IDB_MAIN
      mov hBitmap,eax
   .elseif uMsg==WM_PAINT
      invoke BeginPaint,hWnd,addr ps
      mov    hdc,eax
      invoke CreateCompatibleDC,hdc
      mov    hMemDC,eax
      invoke SelectObject,hMemDC,hBitmap
      invoke GetClientRect,hWnd,addr rect
      invoke BitBlt,hdc,0,0,rect.right,rect.bottom,hMemDC,0,0,SRCCOPY
      invoke DeleteDC,hMemDC
      invoke EndPaint,hWnd,addr ps
 .elseif uMsg==WM_DESTROY
  invoke DeleteObject,hBitmap
  invoke PostQuitMessage,NULL
 .ELSE
  invoke DefWindowProc,hWnd,uMsg,wParam,lParam
  ret
 .ENDIF
 xor eax,eax
 ret
WndProc endp
end start

;---------------------------------------------------------------------
;                            The resource script
;---------------------------------------------------------------------
#define IDB_MAIN 1
IDB_MAIN BITMAP "tweety78.bmp"

Analysis:

There is not much to analyze in this tutorial ;)
#define IDB_MAIN 1
IDB_MAIN BITMAP "tweety78.bmp"
Define a constant named IDB_MAIN, assign 1 as its value. And then use that constant as the bitmap resource identifier. The bitmap file to be included in the resource is "tweety78.bmp" which resides in the same folder as the resource script.

   .if uMsg==WM_CREATE
      invoke LoadBitmap,hInstance,IDB_MAIN
      mov hBitmap,eax

In response to WM_CREATE, we call LoadBitmap to load the bitmap from the resource, passing the bitmap's resource identifier as the second parameter to the API. We get the handle to the bitmap when the function returns.
Now that the bitmap is loaded, we can paint it in the client area of our main window.

   .elseif uMsg==WM_PAINT
      invoke BeginPaint,hWnd,addr ps
      mov    hdc,eax
      invoke CreateCompatibleDC,hdc
      mov    hMemDC,eax
      invoke SelectObject,hMemDC,hBitmap
      invoke GetClientRect,hWnd,addr rect
      invoke BitBlt,hdc,0,0,rect.right,rect.bottom,hMemDC,0,0,SRCCOPY
      invoke DeleteDC,hMemDC
      invoke EndPaint,hWnd,addr ps

We choose to paint the bitmap in response to WM_PAINT message. We first call BeginPaint to obtain the handle to the device context. Then we create a compatible memory DC with CreateCompatibleDC. Next select the bitmap into the memory DC with SelectObject. Determine the dimension of the client area with GetClientRect. Now we can display the bitmap in the client area by calling BitBlt which copies the bitmap from the memory DC to the real DC. When the painting is done, we have no further need for the memory DC so we delete it with DeleteDC. End painting session with EndPaint.

 .elseif uMsg==WM_DESTROY
  invoke DeleteObject,hBitmap
  invoke PostQuitMessage,NULL
When we don't need the bitmap anymore, we delete it with DeleteObject
[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32 Assembly Tutorial 26: Splash Screen

Tutorial 26: Splash Screen

Now that we know how to use a bitmap, we can progress to a more creative use of it. Splash screen. Download the example.

Theory

A splash screen is a window that has no title bar, no system menu box, no border that displays a bitmap for a while and then disappears automatically. It's usually used during program startup, to display the program's logo or to distract the user's attention while the program does some lengthy initialization. We will implement a splash screen in this tutorial.
The first step is to include the bitmap in the resource file. However, if you think of it, it's a waste of precious memory to load a bitmap that will be used only once and keep it in memory till the program is closed. A better solution is to create a *resource* DLL which contains the bitmap and has the sole purpose of displaying the splash screen. This way, you can load the DLL when you want to display the splash screen and unload it when it's not necessary anymore. So we will have two modules: the main program and the splash DLL. We will put the bitmap into the DLL's resource.
The general scheme is as follows:
  1. Put the bitmap into the DLL as a bitmap resource
  2. The main program calls LoadLibrary to load the dll into memory
  3. The DLL entrypoint function of the DLL is called. It will create a timer and set the length of time that the splash screen will be displayed. Next it will register and create a window without caption and border and display the bitmap in the client area.
  4. When the specified length of time elapsed, the splash screen is removed from the screen and the control is returned to the main program
  5. The main program calls FreeLibrary to unload the DLL from memory and then goes on with whatever task it is supposed to do.
We will examine the mechanisms in detail.

Load/Unload DLL

You can dynamically load a DLL with LoadLibrary function which has the following syntax:
LoadLibrary proto lpDLLName:DWORD
It takes only one parameter: the address of the name of the DLL you want to load into memory. If the call is successful, it returns the module handle of the DLL else it returns NULL.
To unload a DLL, call FreeLibrary:
FreeLibrary proto hLib:DWORD
It takes one parameter: the module handle of the DLL you want to unload. Normally, you got that handle from LoadLibrary

How to use a timer

First, you must create a timer first with SetTimer:
SetTimer proto hWnd:DWORD, TimerID:DWORD, uElapse:DWORD, lpTimerFunc:DWORD

hWnd is the handle of a window that will receive the timer notification message. This parameter can be NULL to specify that there is no window that's associated with the timer.
TimerID is a user-defined value that is used as the ID of the timer.
uElapse is the time-out value in milliseconds.
lpTimerFunc is the address of a function that will process the timer notification messages. If you pass NULL, the timer messages will be sent to the window specified by hWnd parameter.

SetTimer returns the ID of the timer if successful. Otherwise it returns NULL. So it's best not to use the timer ID of 0.

You can create a timer in two ways:
  • If you have a window and you want the timer notification messages to go to that window, you must pass all four parameters to SetTimer (the lpTimerFunc must be NULL)
  • If you don't have a window or you don't want to process the timer messages in the window procedure, you must pass NULL to the function in place of a window handle. You must also specify the address of the timer function that will process the timer messages.
We will use the first approach in this example.
When the time-out period elapses, WM_TIMER message is sent to the window that is associated with the timer. For example, if you specify uElapse of 1000, your window will receive WM_TIMER every second.
When you don't need the timer anymore, destroy it with KillTimer:
KillTimer proto hWnd:DWORD, TimerID:DWORD

Example:

;-----------------------------------------------------------------------
;                         The main program
;-----------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "SplashDemoWinClass",0
AppName  db "Splash Screen Example",0
Libname db "splash.dll",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
 invoke LoadLibrary,addr Libname
 .if eax!=NULL
    invoke FreeLibrary,eax
 .endif
 invoke GetModuleHandle, NULL
 mov    hInstance,eax
 invoke GetCommandLine
 mov    CommandLine,eax
 invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
 invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
 LOCAL wc:WNDCLASSEX
 LOCAL msg:MSG
 LOCAL hwnd:HWND
 mov   wc.cbSize,SIZEOF WNDCLASSEX
 mov   wc.style, CS_HREDRAW or CS_VREDRAW
 mov   wc.lpfnWndProc, OFFSET WndProc
 mov   wc.cbClsExtra,NULL
 mov   wc.cbWndExtra,NULL
 push  hInstance
 pop   wc.hInstance
 mov   wc.hbrBackground,COLOR_WINDOW+1
 mov   wc.lpszMenuName,NULL
 mov   wc.lpszClassName,OFFSET ClassName
 invoke LoadIcon,NULL,IDI_APPLICATION
 mov   wc.hIcon,eax
 mov   wc.hIconSm,eax
 invoke LoadCursor,NULL,IDC_ARROW
 mov   wc.hCursor,eax
 invoke RegisterClassEx, addr wc
 INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
           hInst,NULL
 mov   hwnd,eax
 invoke ShowWindow, hwnd,SW_SHOWNORMAL
 invoke UpdateWindow, hwnd
 .while TRUE
  invoke GetMessage, ADDR msg,NULL,0,0
  .break .if (!eax)
  invoke TranslateMessage, ADDR msg
  invoke DispatchMessage, ADDR msg
 .endw
 mov     eax,msg.wParam
 ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
 .IF uMsg==WM_DESTROY
  invoke PostQuitMessage,NULL
 .ELSE
  invoke DefWindowProc,hWnd,uMsg,wParam,lParam
  ret
 .ENDIF
 xor eax,eax
 ret
WndProc endp
end start

;--------------------------------------------------------------------
;                         The Bitmap DLL
;--------------------------------------------------------------------
.386
.model flat, stdcall
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
.data
BitmapName db "MySplashBMP",0
ClassName db "SplashWndClass",0
hBitMap dd 0
TimerID dd 0

.data
hInstance dd ?

.code

DllEntry proc hInst:DWORD, reason:DWORD, reserved1:DWORD
   .if reason==DLL_PROCESS_ATTACH  ; When the dll is loaded
      push hInst
      pop hInstance
      call ShowBitMap
   .endif
   mov eax,TRUE
   ret
DllEntry Endp
ShowBitMap proc
        LOCAL wc:WNDCLASSEX
        LOCAL msg:MSG
        LOCAL hwnd:HWND
        mov   wc.cbSize,SIZEOF WNDCLASSEX
        mov   wc.style, CS_HREDRAW or CS_VREDRAW
        mov   wc.lpfnWndProc, OFFSET WndProc
        mov   wc.cbClsExtra,NULL
        mov   wc.cbWndExtra,NULL
        push  hInstance
        pop   wc.hInstance
        mov   wc.hbrBackground,COLOR_WINDOW+1
        mov   wc.lpszMenuName,NULL
        mov   wc.lpszClassName,OFFSET ClassName
        invoke LoadIcon,NULL,IDI_APPLICATION
        mov   wc.hIcon,eax
        mov   wc.hIconSm,0
        invoke LoadCursor,NULL,IDC_ARROW
        mov   wc.hCursor,eax
        invoke RegisterClassEx, addr wc
        INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\
           WS_POPUP,CW_USEDEFAULT,\
           CW_USEDEFAULT,250,250,NULL,NULL,\
           hInstance,NULL
        mov   hwnd,eax
        INVOKE ShowWindow, hwnd,SW_SHOWNORMAL
        .WHILE TRUE
                INVOKE GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                INVOKE TranslateMessage, ADDR msg
                INVOKE DispatchMessage, ADDR msg
        .ENDW
        mov     eax,msg.wParam
        ret
ShowBitMap endp
WndProc proc hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
        LOCAL ps:PAINTSTRUCT
        LOCAL hdc:HDC
        LOCAL hMemoryDC:HDC
        LOCAL hOldBmp:DWORD
        LOCAL bitmap:BITMAP
        LOCAL DlgHeight:DWORD
        LOCAL DlgWidth:DWORD
        LOCAL DlgRect:RECT
        LOCAL DesktopRect:RECT

        .if uMsg==WM_DESTROY
                .if hBitMap!=0
                        invoke DeleteObject,hBitMap
                .endif
                invoke PostQuitMessage,NULL
        .elseif uMsg==WM_CREATE
                invoke GetWindowRect,hWnd,addr DlgRect
                invoke GetDesktopWindow
                mov ecx,eax
                invoke GetWindowRect,ecx,addr DesktopRect
                push  0
                mov  eax,DlgRect.bottom
                sub  eax,DlgRect.top
                mov  DlgHeight,eax
                push eax
                mov  eax,DlgRect.right
                sub  eax,DlgRect.left
                mov  DlgWidth,eax
                push eax
                mov  eax,DesktopRect.bottom
                sub  eax,DlgHeight
                shr  eax,1
                push eax
                mov  eax,DesktopRect.right
                sub  eax,DlgWidth
                shr  eax,1
                push eax
                push hWnd
                call MoveWindow
                invoke LoadBitmap,hInstance,addr BitmapName
                mov hBitMap,eax
                invoke SetTimer,hWnd,1,2000,NULL
                mov TimerID,eax
        .elseif uMsg==WM_TIMER
                invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL
                invoke KillTimer,hWnd,TimerID
        .elseif uMsg==WM_PAINT
                invoke BeginPaint,hWnd,addr ps
                mov hdc,eax
                invoke CreateCompatibleDC,hdc
                mov hMemoryDC,eax
                invoke SelectObject,eax,hBitMap
                mov hOldBmp,eax
                invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap
                invoke StretchBlt,hdc,0,0,250,250,\
                       hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY
                invoke SelectObject,hMemoryDC,hOldBmp
                invoke DeleteDC,hMemoryDC
                invoke EndPaint,hWnd,addr ps
       .elseif uMsg==WM_LBUTTONDOWN
                invoke DestroyWindow,hWnd
        .else
                invoke DefWindowProc,hWnd,uMsg,wParam,lParam
                ret
        .endif
        xor eax,eax
        ret
WndProc endp

End DllEntry

Analysis:

We will examine the code in the main program first.
 invoke LoadLibrary,addr Libname
 .if eax!=NULL
    invoke FreeLibrary,eax
 .endif
We call LoadLibrary to load the DLL named "splash.dll". And after that, unload it from memory with FreeLibrary. LoadLibrary will not return until the DLL is finished with its initialization.
That's all the main program does. The interesting part is in the DLL.

   .if reason==DLL_PROCESS_ATTACH  ; When the dll is loaded
      push hInst
      pop hInstance
      call ShowBitMap

When the DLL is loaded, Windows calls its entrypoint function with DLL_PROCESS_ATTACH flag. We take this opportunity to display the splash screen. First we store the instance handle of the DLL for future use. Then call a function named ShowBitMap to do the real job. ShowBitMap registers a window class, creates a window and enters the message loop as usual. The interesting part is in the CreateWindowEx call:

        INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\
           WS_POPUP,CW_USEDEFAULT,\
           CW_USEDEFAULT,250,250,NULL,NULL,\
           hInstance,NULL

Note that the window style is only WS_POPUP which will make the window borderless and without caption. We also limit the width and height of the window to 250x250 pixels.
Now when the window is created, in WM_CREATE message handler we move the window to the center of the screen with the following code.

                invoke GetWindowRect,hWnd,addr DlgRect
                invoke GetDesktopWindow
                mov ecx,eax
                invoke GetWindowRect,ecx,addr DesktopRect
                push  0
                mov  eax,DlgRect.bottom
                sub  eax,DlgRect.top
                mov  DlgHeight,eax
                push eax
                mov  eax,DlgRect.right
                sub  eax,DlgRect.left
                mov  DlgWidth,eax
                push eax
                mov  eax,DesktopRect.bottom
                sub  eax,DlgHeight
                shr  eax,1
                push eax
                mov  eax,DesktopRect.right
                sub  eax,DlgWidth
                shr  eax,1
                push eax
                push hWnd
                call MoveWindow

It retrieves the dimensions of the desktop and the window then calculates the appropriate coordinate of the left upper corner of the window to make it center.

                invoke LoadBitmap,hInstance,addr BitmapName
                mov hBitMap,eax
                invoke SetTimer,hWnd,1,2000,NULL
                mov TimerID,eax

Next it loads the bitmap from the resource with LoadBitmap and creates a timer with the timer ID of 1 and the time interval 2 seconds. The timer will send WM_TIMER messages to the window every 2 seconds.

        .elseif uMsg==WM_PAINT
                invoke BeginPaint,hWnd,addr ps
                mov hdc,eax
                invoke CreateCompatibleDC,hdc
                mov hMemoryDC,eax
                invoke SelectObject,eax,hBitMap
                mov hOldBmp,eax
                invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap
                invoke StretchBlt,hdc,0,0,250,250,\
                       hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY
                invoke SelectObject,hMemoryDC,hOldBmp
                invoke DeleteDC,hMemoryDC
                invoke EndPaint,hWnd,addr ps

When the window receives WM_PAINT message, it creates a memory DC, select the bitmap into the memory DC, obtain the size of the bitmap with GetObject and then put the bitmap on the window by calling StretchBlt which performs like BitBlt but it can stretch or compress the bitmap to the desired dimension. In this case, we want the bitmap to fit into the window so we use StretchBlt instead of BitBlt. We delete the memory DC after that.

        .elseif uMsg==WM_LBUTTONDOWN
                invoke DestroyWindow,hWnd

It would be frustrating to the user if he has to wait until the splash screen to disappear. We can provide the user with a choice. When he clicks on the splash screen, it will disappear. That's why we need to process WM_LBUTTONDOWN message in the DLL. Upon receiving this message, the window is destroyed by DestroyWindow call.

        .elseif uMsg==WM_TIMER
                invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL
                invoke KillTimer,hWnd,TimerID

If the user chooses to wait, the splash screen will disappear when the specified time has elapsed (in our example, it's 2 seconds). We can do this by processing WM_TIMER message. Upon receiving this message, we closes the window by sending WM_LBUTTONDOWN message to the window. This is to avoid code duplication. We don't have further use for the timer so we destroy it with KillTimer.
When the window is closed, the DLL will return control to the main program.

[Iczelion's Win32 Assembly HomePage]


Iczelion's Win32asm Tutorial 27: Tooltip Control

Tutorial 27: Tooltip Control

We will learn about the tooltip control: What it is and how to create and use it. Download the example.

Theory:

A tooltip is a small rectangular window that is displayed when the mouse pointer hovers over some specific area. A tooltip window contains some text that the programmer wants to be displayed. In this regard, a tooltip servers the same role as the status window but it disappears when the user clicks or moves the mouse pointer away from the designated area. You'll probably be familiar with the tooltips that are associated with toolbar buttons. Those "tooltips" are conveniencies provided by the toolbar control. If you want tooltips for other windows/controls, you need to create your own tooltip control.
Now that you know what a tooltip is, let's go on to how we can create and use it. The steps are outlined below:
  1. Create a tooltip control with CreateWindowEx
  2. Define a region that the tooltip control will monitor for mouse pointer movement.
  3. Submit the region to the tooltip control
  4. Relay mouse messages of the submitted region to the tooltip control (this step may occur earlier, depending on the method used to relay the messages)
We wll next examine each step in detail.

Tooltip Creation

A tooltip control is a common control. As such, you need to call InitCommonControls somewhere in your source code so that MASM implicitly links your program to comctl32.dll. You create a tooltip control with CreateWindowEx. The typical scenario would be like this:
.data
TooltipClassName db "Tooltips_class32",0
.code
.....
invoke InitCommonControls
invoke CreateWindowEx, NULL, addr TooltipClassName, NULL, TIS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL
Note the window style: TIS_ALWAYSTIP. This style specifies that the tooltip will be shown when the mouse pointer is over the designated area regardless of the status of the window that contains the area. Put simply, if you use this flag, when the mouse pointer hovers over the area you register to the tooltip control, the tooltip window will appear even if the window under the mouse pointer is inactive.
You don't have to include WS_POPUP and WS_EX_TOOLWINDOW styles in CreateWindowEx because the tooltip control's window procedure adds them automatically. You also don't need to specify the coordinate, the height and width of the tooltip window: the tooltip control will adjust them automatically to fit the tooltip text that will be displayed, thus we supply CW_USEDEFAULT in all four parameters. The remaining parameters are not remarkable.

Specifying the tool

The tooltip control is created but it's not shown immediately. We want the tooltip window to show up when the mouse pointer hovers over some area. Now is the time to specify that area. We call such area "tool". A tool is a rectangular area on the client area of a window which the tooltip control will monitor for mouse pointer. If the mouse pointer hovers over the tool, the tooltip window will appear. The rectangular area can cover the whole client area or only a part of it. So we can divided tool into two types: one that is implemented as a window and another that is implemented as a rectangular area in the client area of some window. Both has their uses. The tool that covers the whole client area of a window is most frequently used with controls such as buttons, edit controls and so on. You don't need to specify the coordinate and the dimensions of the tool: it's assumed to be the whole client area of the window. The tool that is implemented as a rectangular area on the client area is useful when you want to divide the client area of a window into several regions without using child windows. With this type of tool, you need to specify the coordinate of the upper left corner and the width and height of the tool.
You specify the tool with theTOOLINFO structure which has the following definition:
TOOLINFO STRUCT
  cbSize             DWORD      ?
  uFlags             DWORD      ?
  hWnd               DWORD      ?
  uId                DWORD      ?
  rect               RECT      <>
  hInst              DWORD      ?
  lpszText           DWORD      ?
  lParam             LPARAM     ?
TOOLINFO ENDS
Field Name | Explanation
cbSize | The size of the TOOLINFO structure. You MUST fill this member. Windows will not flag error if this field is not filled properly but you will receive strange, unpredictable results.
uFlags The bit flags that specifies the characteristics of the tool. This value can be a combination of the following flags:
  • TTF_IDISHWND "ID is hWnd". If you specify this flag, it means you want to use a tool that covers the whole client area of a window (the first type of tool above). If you use this flag, you must fill the uId member of this structure with the handle of the window you want to use. If you don't specify this flag, it means you want to use the second type of tool, the one that is implemented as the rectangular area on the client window. In that case, you need to fill the rect member with the dimension of the rectangle.
  • TTF_CENTERTIP Normally the tooltip window will appear to the right and below the mouse pointer. If you specify this flag, the tooltip window will always appear directly below the tool and is centered regardless of the position of the mouse pointer.
  • TTF_RTLREADING You can forget about this flag if your program is not designed specifically for Arabic or Hebrew systems. This flag displays the tooltip text with right-to-left reading order. Doesn't work under other systems.
  • TTF_SUBCLASS If you use this flag, it means you tell the tooltip control to subclass the window that the tool is on so that the tooltip control can intercept mouse messages that are sent to the window. This flag is very handy. If you don't use this flag, you have to do more work to relay the mouse messages to the tooltip control.
hWnd Handle to the window that contains the tool. If you specify TTF_IDISHWND flag, this field is ignored since Windows will use the value in uId member as the window handle. You need to fill this field if:
  • You don't use TTF_IDISHWND flag (in other words, you use a rectangular tool)
  • You specify the value LPSTR_TEXTCALLBACK in lpszText member. This value tells the tooltip control that, when it needs to display the tooltip window, it must ask the window that contains the tool for the text to be displayed. This is a kind of dynamic realtime tooltip text update. If you want to change your tooltip text dynamically, you should specify LPSTR_TEXTCALLBACK value in lpszText member. The tooltip control will send TTN_NEEDTEXT notification message to the window identified by the handle in hWnd field.
uId The value in this field can have two meanings, depending on whether the uFlags member contains the flag TTF_IDISHWND.
  • Application-defined tool ID if the TTF_IDISHWND flag is not specified. Since this means you use a tool which covers only a part of the client area, it's logical that you can have many such tools on the same client area (without overlap). The tooltip control needs a way to differentiate between them. In this case, the window handle in hWnd member is not enough since all tools are on the same window. The application-defined IDs are thus necessary. The IDs can be any value so long as they are unique among themselves.
  • The handle to the window whose whole client area is used as the tool if the TTF_IDISHWND flag is specified. You may wonder why this field is used to store the window handle instead of the hWnd field above. The answer is: the hWnd member may already be filled if the value LPSTR_TEXTCALLBACK is specified in the lpszText member and the window that is responsible for supplying the tooltip text and the window that contains the tool may NOT be the same ( You can design your program so that a single window can serve both roles but this is too restrictive. In this case, Microsoft gives you more freedom. Cheers.)
rect | A RECT structure that specifies the dimension of the tool. This structure defines a rectangle relative to the upper left corner of the client area of the window specified by the hWnd member. In short, you must fill this structure if you want to specify a tool that covers only a part of the client area. The tooltip control will ignore this field if you specify TTF_IDISHWND flag (you choose to use a tool that covers the whole client area)
hInst | The handle of the instance that contains the string resource that will be used as the tooltip text if the value in the lpszText member specifies the string resource identifier. This may sound confusing. Read the explanation of the lpszText member first and you will understand what this field is used for. The tooltip control ignores this field if the lpszText field doesn't contain a string resource identifier.
lpszText This field can have several values:
  • If you specify the value LPSTR_TEXTCALLBACK in this field, the tooltip control will send TTN_NEEDTEXT notification message to the window identified by the handle in hWnd field for the text string to be displayed in the tooltip window. This is the most dynamic method of tooltip text update: you can change the tooltip text each time the tooltip window is displayed.
  • If you specify a string resource identifier in this field, when the tooltip control needs to display the tooltip text in the tooltip window, it searches for the string in the string table of the instance specified by hInst member. The tooltip control identifies a string resource identifier by checking the high word of this field. Since a string resource identifier is a 16-bit value, the high word of this field will always be zero. This method is useful if you plan to port your program to other languages. Since the string resource is defined in a resource script, you don't need to modify the source code.You only have to modify the string table and the tooltip texts will change without the risk of introducing bugs into your program.
  • If the value in this field is not LPSTR_TEXTCALLBACK and the high word is not zero, the tooltip control interprets the value as the pointer to a text string that will be used as the tooltip text. This method is the easiest to use but the least flexible.

To recapitulate, you need to fill the TOOLINFO structure prior to submitting it to the tooltip control. This structure describes the characteristics of the tool you desire.

Register the tool with the tooltip control

After you fill the TOOLINFO structure, you must submit it to tooltip control. A tooltip control can service many tools so it is usually unnecessary to create more than one tooltip control for a window. To register a tool with a tooltip control, you send the TTM_ADDTOOL message to the tooltip control. The wParam is not used and the lParam must contain the address of the TOOLINFO structure you want to register.
.data?
ti TOOLINFO <>
.......
.code
.......
<fill the TOOLINFO structure>
.......
invoke SendMessage, hwndTooltip, TTM_ADDTOOL, NULL, addr ti
SendMessage for this message will return TRUE if the tool is successfully registered with the tooltip control, FALSE otherwise.
You can unregister the tool by sending TTM_DELTOOL message to the tooltip control.

Relaying Mouse Messages to the Tooltip Control

When the above step is completed, the tooltip control knows which area it should monitor for mouse messages and what text it should display in the tooltip window. The only thing it lacks is the *trigger* for that action. Think about it: the area specified by the tool is on the client area of the other window. How can the tooltip control intercept the mouse messages destined for that window? It needs to do so in order that it can measure the amount of time the mouse pointer hovers over a point in the tool so that when the specified amount of time elapses, the tooltip control shows the tooltip window. There are two methods of accomplishing this goal, one that requires the cooperation of the window that contains the tool and the other without the cooperation on the part of the window.
  • The window that contains the tool must relay the mouse messages to the tooltip control by sending TTM_RELAYEVENT messages to the control. The lParam of this message must contain the address of a MSG structure that specifies the message to be relayed to the tooltip control. A tooltip control processes only the following mouse messages:
    • WM_LBUTTONDOWN
    • WM_MOUSEMOVE
    • WM_LBUTTONUP
    • WM_RBUTTONDOWN
    • WM_MBUTTONDOWN
    • WM_RBUTTONUP
    • WM_MBUTTONUP
    All other messages are ignored. Thus in the window procedure of the window that contains the tool, there must be a switch that does something like this:

    WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .......
    if uMsg==WM_CREATE
    .............
    elseif uMsg==WM_LBUTTONDOWN || uMsg==WM_MOUSEMOVE || uMsg==WM_LBUTTONUP || uMsg==WM_RBUTTONDOWN || uMsg==WM_MBUTTONDOWN || uMsg==WM_RBUTTONUP || uMsg==WM_MBUTTONUP
    invoke SendMessage, hwndTooltip, TTM_RELAYEVENT, NULL, addr msg
    ..........

  • You can specify TTF_SUBCLASS flag in the uFlags member of the TOOLINFO structure. This flag tells the tooltip control to subclass the window that contains the tool so it can intercept the mouse messages without the cooperation of the window. This method is easier to use since it doesn't require more coding than specifying TTF_SUBCLASS flag and the tooltip control handles all the message interception itself.
That's it. At this step, your tooltip control is fully functional. There are several useful tooltip-related messages you should know about.
  • TTM_ACTIVATE. If you want to disable/enable the tooltip control dynamically, this message is for you. If the wParam value is TRUE, the tooltip control is enabled. If the wParam value is FALSE, the tooltip control is disabled. A tooltip control is enabled when it first created so you don't need to send this message to activate it.
  • TTM_GETTOOLINFO and TTM_SETTOOLINFO. If you want to obtain/change the values in the TOOLINFO structure after it was submitted to the tooltip control, use these messages. You need to specify the tool you need to change with the correct uId and hWnd values. If you only want to change the rect member, use TTM_NEWTOOLRECT message. If you only want to change the tooltip text, use TTM_UPDATETIPTEXT.
  • TTM_SETDELAYTIME. With this message, you can specify the time delay the tooltip control uses when it's displaying the tooltip text and much more.

Example:

The following example is a simple dialog box with two buttons. The client area of the dialog box is divided into 4 areas: upper left, upper right, lower left and lower right. Each area is specified as a tool with its own tooltip text. The two buttons also has their own tooltip texts.
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
EnumChild proto :DWORD,:DWORD
SetDlgToolArea proto :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
.const
IDD_MAINDIALOG equ 101
.data
ToolTipsClassName db "Tooltips_class32",0
MainDialogText1 db "This is the upper left area of the dialog",0
MainDialogText2 db "This is the upper right area of the dialog",0
MainDialogText3 db "This is the lower left area of the dialog",0
MainDialogText4 db "This is the lower right area of the dialog",0
.data?
hwndTool dd ?
hInstance dd ?
.code
start:
    invoke GetModuleHandle,NULL
    mov hInstance,eax
    invoke DialogBoxParam,hInstance,IDD_MAINDIALOG,NULL,addr DlgProc,NULL
    invoke ExitProcess,eax

DlgProc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
    LOCAL ti:TOOLINFO
    LOCAL id:DWORD
    LOCAL rect:RECT
    .if uMsg==WM_INITDIALOG
        invoke InitCommonControls
        invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\
            TTS_ALWAYSTIP,CW_USEDEFAULT,\
            CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
            hInstance,NULL
        mov hwndTool,eax
        mov id,0
        mov ti.cbSize,sizeof TOOLINFO
        mov ti.uFlags,TTF_SUBCLASS
        push hDlg
        pop ti.hWnd
        invoke GetWindowRect,hDlg,addr rect
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect
        inc id
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText2,id,addr rect
        inc id
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText3,id,addr rect
        inc id
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText4,id,addr rect
        invoke EnumChildWindows,hDlg,addr EnumChild,addr ti
    .elseif uMsg==WM_CLOSE
        invoke EndDialog,hDlg,NULL
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
DlgProc endp

EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD
    LOCAL buffer[256]:BYTE
    mov edi,lParam
    assume edi:ptr TOOLINFO
    push hwndChild
    pop [edi].uId
    or [edi].uFlags,TTF_IDISHWND
    invoke GetWindowText,hwndChild,addr buffer,255
    lea eax,buffer
    mov [edi].lpszText,eax
    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi
    assume edi:nothing
    ret
EnumChild endp

SetDlgToolArea proc uses edi esi hDlg:DWORD,lpti:DWORD,lpText:DWORD,id:DWORD,lprect:DWORD
    mov edi,lpti
    mov esi,lprect
    assume esi:ptr RECT
    assume edi:ptr TOOLINFO
    .if id==0
        mov [edi].rect.left,0
        mov [edi].rect.top,0
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        shr eax,1
        mov [edi].rect.bottom,eax
    .elseif id==1
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        inc eax
        mov [edi].rect.left,eax
        mov [edi].rect.top,0
        mov eax,[esi].right
        sub eax,[esi].left
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        mov [edi].rect.bottom,eax
    .elseif id==2
        mov [edi].rect.left,0
        mov eax,[esi].bottom
        sub eax,[esi].top
        shr eax,1
        inc eax
        mov [edi].rect.top,eax
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        mov [edi].rect.bottom,eax
    .else
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        inc eax
        mov [edi].rect.left,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        shr eax,1
        inc eax
        mov [edi].rect.top,eax
        mov eax,[esi].right
        sub eax,[esi].left
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        mov [edi].rect.bottom,eax
    .endif
    push lpText
    pop [edi].lpszText
    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti
    assume edi:nothing
    assume esi:nothing
    ret
SetDlgToolArea endp
end start

Analysis:

After the main dialog window is created, we create the tooltip control with CreateWindowEx.
invoke InitCommonControls
invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\
       TTS_ALWAYSTIP,CW_USEDEFAULT,\
       CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
       hInstance,NULL
mov hwndTool,eax
After that, we proceed to define four tools for each corner of the dialog box.

    mov id,0        ; used as the tool ID
    mov ti.cbSize,sizeof TOOLINFO
    mov ti.uFlags,TTF_SUBCLASS   ; tell the tooltip control to subclass the dialog window.
    push hDlg
    pop ti.hWnd    ; handle to the window that contains the tool
    invoke GetWindowRect,hDlg,addr rect   ; obtain the dimension of the client area
    invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect

We initialize the members of TOOLINFO structure. Note that we want to divide the client area into 4 tools so we need to know the dimension of the client area. That's why we call GetWindowRect. We don't want to relay mouse messages to the tooltip control ourselves so we specify TIF_SUBCLASS flag.
SetDlgToolArea is a function that calculates the bounding rectangle of each tool and registers the tool to the tooltip control. I won't go into gory detail on the calculation, suffice to say that it divides the client area into 4 areas with the same sizes. Then it sends TTM_ADDTOOL message to the tooltip control, passing the address of the TOOLINFO structure in the lParam parameter.

    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti

After all 4 tools are registered, we can go on to the buttons on the dialog box. We can handle each button by its ID but this is tedious. Instead, we will use EnumChildWindows API call to enumerate all controls on the dialog box and then registers them to the tooltip control. EnumChildWindows has the following syntax:

EnumChildWindows proto hWnd:DWORD, lpEnumFunc:DWORD, lParam:DWORD
hWnd is the handle to the parent window. lpEnumFunc is the address of the EnumChildProc function that will be called for each control enumerated. lParam is the application-defined value that will be passed to the EnumChildProc function. The EnumChildProc function has the following definition:
EnumChildProc proto hwndChild:DWORD, lParam:DWORD
hwndChild is the handle to a control enumerated by EnumChildWindows. lParam is the same lParam value you pass to EnumChildWindows.
In our example, we callEnumChildWindows like this:
invoke EnumChildWindows,hDlg,addr EnumChild,addr ti
We pass the address of the TOOLINFO structure in the lParam parameter because we will register each child control to the tooltip control in the EnumChild function. If we don't use this method, we need to declare ti as a global variable which can introduce bugs.
When we call EnumChildWindows, Windows will enumerate the child controls on our dialog box and call the EnumChild function once for each control enumerated. Thus if our dialog box has two controls, EnumChild will be called twice.
The EnumChild function fills the relevant members of the TOOLINFO structure and then registers the tool with the tooltip control.
EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD
    LOCAL buffer[256]:BYTE
    mov edi,lParam
    assume edi:ptr TOOLINFO
    push hwndChild
    pop [edi].uId    ; we use the whole client area of the control as the tool
    or [edi].uFlags,TTF_IDISHWND
    invoke GetWindowText,hwndChild,addr buffer,255
    lea eax,buffer   ; use the window text as the tooltip text
    mov [edi].lpszText,eax
    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi
    assume edi:nothing
    ret
EnumChild endp
Note that in this case, we use a different type of tool: one that covers the whole client area of the window. We thus need to fill the uID field with the handle to the window that contains the tool. Also we must specify TTF_IDISHWND flag in the uFlags member.
[Iczelion's Win32 Assembly Homepage]


Iczelion's Win32 Assembly Tutorial 28: Win32 Debug API Part 1

Tutorial 28: Win32 Debug API Part 1

In this tutorial, you'll learn what Win32 offers to developers regarding debugging primitives. You'll know how to debug a process when you're finished with this tutorial.
Download the example.

Theory:

Win32 has several APIs that allow programmers to use some of the powers of a debugger. They are called Win32 Debug APIs or primitives. With them, you can:

  • Load a program or attach to a running program for debugging
  • Obtain low-level information about the program you're debugging, such as process ID, address of entrypoint, image base and so on.
  • Be notified of debugging-related events such as when a process/thread starts/exits, DLLs are loaded/unloaded etc.
  • Modify the process/thread being debugged

In short, you can code a simple debugger with those APIs. Since this subject is vast, I divide it into several managable parts: this tutorial being the first part. I'll explain the basic concepts and general framework for using Win32 Debug APIs in this tutorial.
The steps in using Win32 Debug APIs are:

  1. Create a process or attach your program to a running process. This is the first step in using Win32 Debug APIs. Since your program will act as a debugger, you need a program to debug. The program being debugged is called a debuggee. You can acquire a debuggee in two ways:
    • You can create the debuggee process yourself with CreateProcess. In order to create a process for debugging, you must specify the DEBUG_PROCESS flag. This flag tells Windows that we want to debug the process. Windows will send notifications of important debugging-related events (debug events) that occur in the debuggee to your program. The debuggee process will be immediately suspended until your program is ready. If the debuggee also creates child processes, Windows will also send debug events that occur in all those child processes to your program as well. This behavior is usually undesirable. You can disable this behavior by specifying DEBUG_ONLY_THIS_PROCESS flag in combination of DEBUG_PROCESS flag.
    • You can attach your program to a running process with DebugActiveProcess.
  2. Wait for debugging events. After your program acquired a debuggee, the debuggee's primary thread is suspended and will continue to be suspended until your program calls WaitForDebugEvent. This function works like other WaitForXXX functions, ie. it blocks the calling thread until the waited-for event occurs. In this case, it waits for debug events to be sent by Windows. Let's see its definition:

    WaitForDebugEvent proto lpDebugEvent:DWORD, dwMilliseconds:DWORD

    lpDebugEvent is the address of a DEBUG_EVENT structure that will be filled with information about the debug event that occurs within the debuggee.

    dwMilliseconds is the length of time in milliseconds this function will wait for the debug event to occur. If this period elapses and no debug event occurs, WaitForDebugEvent returns to the caller. On the other hand, if you specify INFINITE constant in this argument, the function will not return until a debug event occurs.

    Now let's examine the DEBUG_EVENT structure in more detail.

    DEBUG_EVENT STRUCT
    dwDebugEventCode dd ?
    dwProcessId dd ?
    dwThreadId dd ?
    u DEBUGSTRUCT <>
    DEBUG_EVENT ENDS

    dwDebugEventCode contains the value that specifies what type of debug event occurs. In short, there can be many types of events, your program needs to check the value in this field so it knows what type of event occurs and responds appropriately. The possible values are:

    3. Value | Meanings
CREATE_PROCESS_DEBUG_EVENT | A process is created. This event will be sent when the debuggee process is just created (and not yet running) or when your program just attaches itself to a running process with DebugActiveProcess. This is the first event your program will receive.
EXIT_PROCESS_DEBUG_EVENT | A process exits.
CREATE_THEAD_DEBUG_EVENT | A new thread is created in the debuggee process or when your program first attaches itself to a running process. Note that you'll not receive this notification when the primary thread of the debuggee is created.
EXIT_THREAD_DEBUG_EVENT | A thread in the debuggee process exits. Your program will not receive this event for the primary thread. In short, you can think of the primary thread of the debuggee as the equivalent of the debuggee process itself. Thus, when your program sees CREATE_PROCESS_DEBUG_EVENT, it's actually the CREATE_THREAD_DEBUG_EVENT for the primary thread.
LOAD_DLL_DEBUG_EVENT | The debuggee loads a DLL. You'll receive this event when the PE loader first resolves the links to DLLs (you call CreateProcess to load the debuggee) and when the debuggee calls LoadLibrary.
UNLOAD_DLL_DEBUG_EVENT | A DLL is unloaded from the debuggee process.
EXCEPTION_DEBUG_EVENT | An exception occurs in the debuggee process. Important: This event will occur once just before the debuggee starts executing its first instruction. The exception is actually a debug break (int 3h). When you want to resume the debuggee, call ContinueDebugEvent with DBG_CONTINUE flag. Don't use DBG_EXCEPTION_NOT_HANDLED flag else the debuggee will refuse to run under NT (on Win98, it works fine).
OUTPUT_DEBUG_STRING_EVENT | This event is generated when the debuggee calls DebugOutputString function to send a message string to your program.
RIP_EVENT | System debugging error occurs

    dwProcessId and dwThreadId are the process and thread Ids of the process that the debug event occurs. You can use these values as identifiers of the process/thread you're interested in. Remember that if you use CreateProcess to load the debuggee, you also get the process and thread IDs of the debuggee in the PROCESS_INFO structure. You can use these values to differentiate between the debug events occurring in the debuggee and its child processes (in case you didn't specify DEBUG_ONLY_THIS_PROCESS flag).

    u is a union that contains more information about the debug event. It can be one of the following structures depending on the value of dwDebugEventCode above.

    value in dwDebugEventCode | Interpretation of u
CREATE_PROCESS_DEBUG_EVENT | A CREATE_PROCESS_DEBUG_INFO structure named CreateProcessInfo
EXIT_PROCESS_DEBUG_EVENT | An EXIT_PROCESS_DEBUG_INFO structure named ExitProcess
CREATE_THREAD_DEBUG_EVENT | A CREATE_THREAD_DEBUG_INFO structure named CreateThread
EXIT_THREAD_DEBUG_EVENT | An EXIT_THREAD_DEBUG_EVENT structure named ExitThread
LOAD_DLL_DEBUG_EVENT | A LOAD_DLL_DEBUG_INFO structure named LoadDll
UNLOAD_DLL_DEBUG_EVENT | An UNLOAD_DLL_DEBUG_INFO structure named UnloadDll
EXCEPTION_DEBUG_EVENT | An EXCEPTION_DEBUG_INFO structure named Exception
OUTPUT_DEBUG_STRING_EVENT | An OUTPUT_DEBUG_STRING_INFO structure named DebugString
RIP_EVENT | A RIP_INFO structure named RipInfo

    I won't go into detail about all those structures in this tutorial, only the CREATE_PROCESS_DEBUG_INFO structure will be covered here.
    Assuming that our program calls WaitForDebugEvent and it returns. The first thing we should do is to examine the value in dwDebugEventCode to see which type of debug event occured in the debuggee process. For example, if the value in dwDebugEventCode is CREATE_PROCESS_DEBUG_EVENT, you can interpret the member in u as CreateProcessInfo and access it with u.CreateProcessInfo.

  3. Do whatever your program want to do in response to the debug event. When WaitForDebugEvent returns, it means a debug event just occurred in the debuggee process or a timeout occurs. Your program needs to examine the value in dwDebugEventCode in order to react to the event appropriately. In this regard, it's like processing Windows messages: you choose to handle some and ignore some.
  4. Let the debuggee continues execution. When a debug event occurs, Windows suspends the debuggee. When you're finished with the event handling, you need to kick the debuggee into moving again. You do this by calling ContinueDebugEvent function.

    ContinueDebugEvent proto dwProcessId:DWORD, dwThreadId:DWORD, dwContinueStatus:DWORD

    This function resumes the thread that was previously suspended because a debug event occurred.
    dwProcessId and dwThreadId are the process and thread IDs of the thread that will be resumed. You usually take these two values from the dwProcessId and dwThreadId members of the DEBUG_EVENT structure.
    dwContinueStatus specifies how to continue the thread that reported the debug event. There are two possible values: DBG_CONTINUE and DBG_EXCEPTION_NOT_HANDLED. For all other debug events, those two values do the same thing: resume the thread. The exception is the EXCEPTION_DEBUG_EVENT. If the thread reports an exception debug event, it means an exception occurred in the debuggee thread. If you specify DBG_CONTINUE, the thread will ignore its own exception handling and continue with the execution. In this scenario, your program must examine and resolve the exception itself before resuming the thread with DBG_CONTINUE else the exception will occur again and again and again.... If you specify DBG_EXCEPTION_NOT_HANDLED, your program is telling Windows that it didn't handle the exception: Windows should use the default exception handler of the debuggee to handle the exception.
    In conclusion, if the debug event refers to an exception in the debuggee process, you should call ContinueDebugEvent with DBG_CONTINUE flag if your program already removed the cause of exception. Otherwise, your program must call ContinueDebugEvent with DBG_EXCEPTION_NOT_HANDLED flag. Except in one case which you must always use DBG_CONTINUE flag: the first EXCEPTION_DEBUG_EVENT which has the value EXCEPTION_BREAKPOINT in the ExceptionCode member. When the debuggee is going to execute its very first instruction, your program will receive the exception debug event. It's actually a debug break (int 3h). If you respond by calling ContinueDebugEvent with DBG_EXCEPTION_NOT_HANDLED flag, Windows NT will refuse to run the debuggee (because no one cares for it). You must always use DBG_CONTINUE flag in this case to tell Windows that you want the thread to go on.

  5. Continue this cycle in an infinite loop until the debuggee process exits. Your program must be in an infinite loop much like a message loop until the debuggee exits. The loop looks like this:

    .while TRUE
    invoke WaitForDebugEvent, addr DebugEvent, INFINITE
    .break .if DebugEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
    <Handle the debug events>
    invoke ContinueDebugEvent, DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
    .endw

    Here's the catch: Once you start debugging a program, you just can't detach from the debuggee until it exits.

Let's summarize the steps again:

  1. Create a process or attach your program to a running process.
  2. Wait for debugging events
  3. Do whatever your program want to do in response to the debug event.
  4. Let the debuggee continues execution.
  5. Continue this cycle in an infinite loop until the debuggee process exits

Example:

This example debugs a win32 program and shows important information such as the process handle, process Id, image base and so on.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib
.data
AppName db "Win32 Debug Example no.1",0
ofn OPENFILENAME <>
FilterString db "Executable Files",0,"*.exe",0
             db "All Files",0,"*.*",0,0
ExitProc db "The debuggee exits",0
NewThread db "A new thread is created",0
EndThread db "A thread is destroyed",0
ProcessInfo db "File Handle: %lx ",0dh,0Ah
            db "Process Handle: %lx",0Dh,0Ah
            db "Thread Handle: %lx",0Dh,0Ah
            db "Image Base: %lx",0Dh,0Ah
            db "Start Address: %lx",0
.data?
buffer db 512 dup(?)
startinfo STARTUPINFO <>
pi PROCESS_INFORMATION <>
DBEvent DEBUG_EVENT <>
.code
start:
mov ofn.lStructSize,sizeof ofn
mov ofn.lpstrFilter, offset FilterString
mov ofn.lpstrFile, offset buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke GetStartupInfo,addr startinfo
invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi
.while TRUE
   invoke WaitForDebugEvent, addr DBEvent, INFINITE
   .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION
       .break
   .elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
       invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress
       invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION
   .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
       .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
          invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
         .continue
       .endif
   .elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .endif
   invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw
invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread
.endif
invoke ExitProcess, 0
end start

Analysis:

The program fills the OPENFILENAME structure and then calls GetOpenFileName to let the user choose a program to be debugged.

invoke GetStartupInfo,addr startinfo
invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi

When the user chose one, it calls CreateProcess to load the program. It calls GetStartupInfo to fill the STARTUPINFO structure with its default values. Note that we use DEBUG_PROCESS combined with DEBUG_ONLY_THIS_PROCESS flags in order to debug only this program, not including its child processes.

.while TRUE
   invoke WaitForDebugEvent, addr DBEvent, INFINITE

When the debuggee is loaded, we enter the infinite debug loop, calling WaitForDebugEvent. WaitForDebugEvent will not return until a debug event occurs in the debuggee because we specify INFINITE as its second parameter. When a debug event occurred, WaitForDebugEvent returns and DBEvent is filled with information about the debug event.

   .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION
       .break

We first check the value in dwDebugEventCode. If it's EXIT_PROCESS_DEBUG_EVENT, we display a message box saying "The debuggee exits" and then get out of the debug loop.

   .elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
       invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress
       invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION

If the value in dwDebugEventCode is CREATE_PROCESS_DEBUG_EVENT, then we display several interesting information about the debuggee in a message box. We obtain those information from u.CreateProcessInfo. CreateProcessInfo is a structure of type CREATE_PROCESS_DEBUG_INFO. You can get more info about this structure from Win32 API reference.

   .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
       .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
          invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
         .continue
       .endif

If the value in dwDebugEventCode is EXCEPTION_DEBUG_EVENT, we must check further for the exact type of exception. It's a long line of nested structure reference but you can obtain the kind of exception from ExceptionCode member. If the value in ExceptionCode is EXCEPTION_BREAKPOINT and it occurs for the first time (or if we are sure that the debuggee has no embedded int 3h), we can safely assume that this exception occured when the debuggee was going to execute its very first instruction. When we are done with the processing, we must call ContinueDebugEvent with DBG_CONTINUE flag to let the debuggee run. Then we go back to wait for the next debug event.

   .elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .endif

If the value in dwDebugEventCode is CREATE_THREAD_DEBUG_EVENT or EXIT_THREAD_DEBUG_EVENT, we display a message box saying so.

   invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw

Except for the EXCEPTION_DEBUG_EVENT case above, we call ContinueDebugEvent with DBG_EXCEPTION_NOT_HANDLED flag to resume the debuggee.

invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread

When the debuggee exits, we are out of the debug loop and must close both process and thread handles of the debuggee. Closing the handles doesn't mean we are killing the process/thread. It just means we don't want to use those handles to refer to the process/thread anymore.

[Iczelion's Win32 Assembly Homepage]


Iczelion's Win32 Assembly Tutorial 29: Win32 Debug API part 2

Tutorial 29: Win32 Debug API Part 2

We continue with the subject of win32 debug API. In this tutorial, we will learn how to modify the debuggee process.
Download the example

Theory:

In the previous tutorial, we know how to load the debuggee and handle debug events that occur in its process. In order to be useful, our program must be able to modify the debuggee process. There are several APIs just for this purpose.

  • ReadProcessMemory This function allows you to read memory in the specified process. The function prototype is as follows:

    ReadProcessMemory proto hProcess:DWORD, lpBaseAddress:DWORD, lpBuffer:DWORD, nSize:DWORD, lpNumberOfBytesRead:DWORD

    hProcess is the handle to the process you want to read.
    lpBaseAddress is the address in the target process you want to start reading. For example, if you want to read 4 bytes from the debuggee process starting at 401000h, the value in this parameter must be 401000h.
    lpBuffer is the address of the buffer to receive the bytes read from the process.
    nSize is the number of bytes you want to read
    lpNumberOfBytesRead is the address of the variable of dword size that receives the number of bytes actually read. If you don't care about it, you can use NULL.

  • WriteProcessMemory is the counterpart of ReadProcessMemory. It enables you to write memory of the target process. Its parameters are exactly the same as those of ReadProcessMemory

    The next two API functions need a little background on context. Under a multitasking OS like Windows, there can be several programs running at the same time. Windows gives each thread a timeslice. When that timeslice expires, Windows freezes the present thread and switches to the next thread that has the highest priority. Just before switching to the other thread, Windows saves values in registers of the present thread so that when the time comes to resume the thread, Windows can restore the last *environment* of that thread. The saved values of the registers are collectively called a context.
    Back to our subject. When a debug event occurs, Windows suspends the debuggee. The debuggee's context is saved. Since the debuggee is suspended, we can be sure that the values in the context will remain unchanged . We can get the values in the context with GetThreadContext and we can change them with SetThreadContext.
    These two APIs are very powerful. With them, you have at your fingertips the VxD-like power over the debuggee: you can alter the saved register values and just before the debuggee resumes execution, the values in the context will be written back into the registers. Any change you made to the context is reflected back to the debuggee. Think about it: you can even alter the value of the eip register and divert the flow of execution to anywhere you like! You won't be able to do that under normal circumstance.

    GetThreadContext proto hThread:DWORD, lpContext:DWORD

    hThread is the handle to the thread that you want to obtain the context from
    lpContext is the address of the CONTEXT structure that will be filled when the function returns successfully.

    SetThreadContext has exactly the same parameters. Let's see what a CONTEXT structure looks like:

  • CONTEXT STRUCT
  • ContextFlags dd ?
    ;----------------------------------------------------------------------------------------------------------
    ; This section is returned if ContextFlags contains the value CONTEXT_DEBUG_REGISTERS
  • ;-----------------------------------------------------------------------------------------------------------
    iDr0 dd ?
    iDr1 dd ?
    iDr2 dd ?
    iDr3 dd ?
    iDr6 dd ?
    iDr7 dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ; This section is returned if ContextFlags contains the value CONTEXT_FLOATING_POINT
  • ;-----------------------------------------------------------------------------------------------------------
  • FloatSave FLOATING_SAVE_AREA <>
  • ;----------------------------------------------------------------------------------------------------------
    ; This section is returned if ContextFlags contains the value CONTEXT_SEGMENTS
  • ;-----------------------------------------------------------------------------------------------------------
  • regGs dd ?
    regFs dd ?
    regEs dd ?
    regDs dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ; This section is returned if ContextFlags contains the value CONTEXT_INTEGER
  • ;-----------------------------------------------------------------------------------------------------------
  • regEdi dd ?
    regEsi dd ?
    regEbx dd ?
    regEdx dd ?
    regEcx dd ?
    regEax dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ; This section is returned if ContextFlags contains the value CONTEXT_CONTROL
  • ;-----------------------------------------------------------------------------------------------------------
  • regEbp dd ?
    regEip dd ?
    regCs dd ?
    regFlag dd ?
    regEsp dd ?
    regSs dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ; This section is returned if ContextFlags contains the value CONTEXT_EXTENDED_REGISTERS
  • ;-----------------------------------------------------------------------------------------------------------
  • ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?) CONTEXT ENDS

    As you can observe, the members of this structures are mimics of the real processor's registers. Before you can use this structure, you need to specify which groups of registers you want to read/write in ContextFlags member. For example, if you want to read/write all registers, you must specify CONTEXT_FULL in ContextFlags. If you want only to read/write regEbp, regEip, regCs, regFlag, regEsp or regSs, you must specify CONTEXT_CONTROL in ContextFlags.

    One thing you must remember when using the CONTEXT structure: it must be aligned on dword boundary else you'd get strange results under NT. You must put "align dword" just above the line that declares it, like this:

    align dword
    MyContext CONTEXT <>

Example:

The first example demonstrates the use of DebugActiveProcess. First, you need to run a target named win.exe which goes in an infinite loop just before the window is shown on the screen. Then you run the example, it will attach itself to win.exe and modify the code of win.exe such that win.exe exits the infinite loop and shows its own window.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib

.data
AppName db "Win32 Debug Example no.2",0
ClassName db "SimpleWinClass",0
SearchFail db "Cannot find the target process",0
TargetPatched db "Target patched!",0
buffer dw 9090h

.data?
DBEvent DEBUG_EVENT <>
ProcessId dd ?
ThreadId dd ?
align dword
context CONTEXT <>

.code
start:
invoke FindWindow, addr ClassName, NULL
.if eax!=NULL
    invoke GetWindowThreadProcessId, eax, addr ProcessId
    mov ThreadId, eax
    invoke DebugActiveProcess, ProcessId
    .while TRUE
       invoke WaitForDebugEvent, addr DBEvent, INFINITE
       .break .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
          mov context.ContextFlags, CONTEXT_CONTROL
          invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
          invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL
          invoke MessageBox, 0, addr TargetPatched, addr AppName, MB_OK+MB_ICONINFORMATION
       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
          .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
             invoke ContinueDebugEvent, DBEvent.dwProcessId,DBEvent.dwThreadId, DBG_CONTINUE
             .continue
          .endif
       .endif
       invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
   .endw
.else
    invoke MessageBox, 0, addr SearchFail, addr AppName,MB_OK+MB_ICONERROR .endif
invoke ExitProcess, 0
end start

;--------------------------------------------------------------------
; The partial source code of win.asm, our debuggee. It's actually
; the simple window example in tutorial 2 with an infinite loop inserted
; just before it enters the message loop.
;----------------------------------------------------------------------

......
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL
mov hwnd,eax
jmp $ <---- Here's our infinite loop. It assembles to EB FE
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.while TRUE
   invoke GetMessage, ADDR msg,NULL,0,0
   .break .if (!eax)
   invoke TranslateMessage, ADDR msg
   invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

Analysis:

invoke FindWindow, addr ClassName, NULL

Our program needs to attach itself to the debuggee with DebugActiveProcess which requires the process Id of the debuggee. We can obtain the process Id by calling GetWindowThreadProcessId which in turn needs the window handle as its parameter. So we need to obtain the window handle first.
With FindWindow, we can specify the name of the window class we need. It returns the handle to the window created by that window class. If it returns NULL, no window of that class is present.

.if eax!=NULL
    invoke GetWindowThreadProcessId, eax, addr ProcessId
    mov ThreadId, eax
    invoke DebugActiveProcess, ProcessId

After we obtain the process Id, we can call DebugActiveProcess. Then we enter the debug loop waiting for the debug events.

       .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
          mov context.ContextFlags, CONTEXT_CONTROL
          invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context

When we get CREATE_PROCESS_DEBUG_INFO, it means the debuggee is suspended, ready for us to do surgery upon its process. In this example, we will overwrite the infinite loop instruction in the debuggee (0EBh 0FEh) with NOPs ( 90h 90h).
First, we need to obtain the address of the instruction. Since the debuggee is already in the loop by the time our program attached to it, eip will always point to the instruction. All we need to do is obtain the value of eip. We use GetThreadContext to achieve that goal. We set the ContextFlags member to CONTEXT_CONTROL so as to tell GetThreadContext that we want it to fill the "control" register members of the CONTEXT structure.

          invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL

Now that we get the value of eip, we can call WriteProcessMemory to overwrite the "jmp $" instruction with NOPs, thus effectively help the debuggee exit the infinite loop. After that we display the message to the user and then call ContinueDebugEvent to resume the debuggee. Since the "jmp $" instruction is overwritten by NOPs, the debuggee will be able to continue with showing its window and enter the message loop. The evidence is we will see its window on screen.

The other example uses a slightly different approach to break the debuggee out of the infinite loop.

.......
.......
.if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
   mov context.ContextFlags, CONTEXT_CONTROL
   invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
   add context.regEip,2
   invoke SetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
   invoke MessageBox, 0, addr LoopSkipped, addr AppName, MB_OK+MB_ICONINFORMATION
.......
.......

It still calls GetThreadContext to obtain the current value of eip but instead of overwriting the "jmp $" instruction, it increments the value of regEip by 2 to "skip over" the instruction. The result is that when the debuggee regains control , it resumes execution at the next instruction after "jmp $".

Now you can see the power of Get/SetThreadContext. You can also modify the other register images as well and their values will be reflected back to the debuggee. You can even insert int 3h instruction to put breakpoints in the debuggee process.

[Iczelion's Win32 Assembly Homepage]


Iczelion's Win32 Assembly Tutorial 30: Win32 Debug API part 3

Tutorial 30: Win32 Debug API part 3

In this tutorial, we continue the exploration of win32 debug api. Specifically, we will learn how to trace the debuggee.
Download the example.

Revision history:

12/2/2000: Forgot to dword-align the CONTEXT structure

Theory:

If you have used a debugger before, you would be familiar with tracing. When you "trace" a program, the program stops after executing each instruction, giving you the chance to examine the values of registers/memory. Single-stepping is the official name of tracing.
The single-step feature is provided by the CPU itself. The 8th bit of the flag register is called trap flag. If this flag(bit) is set, the CPU executes in single-step mode. The CPU will generate a debug exception after each instruction. After the debug exception is generated, the trap flag is cleared automatically.
We can also single-step the debuggee, using win32 debug api. The steps are as follows:

  1. Call GetThreadContext, specifying CONTEXT_CONTROL in ContextFlags, to obtain the value of the flag register.
  2. Set the trap bit in regFlag member of the CONTEXT structure
  3. call SetThreadContext
  4. Wait for the debug events as usual. The debuggee will execute in single-step mode. After it executes each instruction, we will get EXCEPTION_DEBUG_EVENT with EXCEPTION_SINGLE_STEP value in u.Exception.pExceptionRecord.ExceptionCode
  5. If you need to trace the next instruction, you need to set the trap bit again.

Example:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib

.data
AppName db "Win32 Debug Example no.4",0
ofn OPENFILENAME <>
FilterString db "Executable Files",0,"*.exe",0
             db "All Files",0,"*.*",0,0
ExitProc db "The debuggee exits",0Dh,0Ah
         db "Total Instructions executed : %lu",0
TotalInstruction dd 0

.data?
buffer db 512 dup(?)
startinfo STARTUPINFO <>
pi PROCESS_INFORMATION <>
DBEvent DEBUG_EVENT <>
align dword
context CONTEXT <>

.code
start:
mov ofn.lStructSize,SIZEOF ofn
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
    invoke GetStartupInfo,addr startinfo
    invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi
    .while TRUE
       invoke WaitForDebugEvent, addr DBEvent, INFINITE
       .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
          invoke wsprintf, addr buffer, addr ExitProc, TotalInstruction
          invoke MessageBox, 0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION
          .break
       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT           .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
             mov context.ContextFlags, CONTEXT_CONTROL
             invoke GetThreadContext, pi.hThread, addr context
             or context.regFlag,100h
             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
             .continue
          .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP
             inc TotalInstruction
             invoke GetThreadContext,pi.hThread,addr context or context.regFlag,100h
             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE
             .continue
          .endif
       .endif
       invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
    .endw
.endif
invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread
invoke ExitProcess, 0
end start

Analysis:

The program shows the openfile dialog box. When the user chooses an executable file, it executes the program in single-step mode, couting the number of instructions executed until the debuggee exits.

       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT           .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT

We take this opportunity to set the debuggee into single-step mode. Remember that Windows sends an EXCEPTION_BREAKPOINT just before it executes the first instruction of the debuggee.

             mov context.ContextFlags, CONTEXT_CONTROL
             invoke GetThreadContext, pi.hThread, addr context

We call GetThreadContext to fill the CONTEXT structure with the current values in the registers of the debuggee. More specifically, we need the current value of the flag register.

             or context.regFlag,100h

We set the trap bit (8th bit) in the flag register image.

             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
             .continue

Then we call SetThreadContext to overwrite the values in the CONTEXT structure with the new one(s) and call ContinueDebugEvent with DBG_CONTINUE flag to resume the debuggee.

          .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP
             inc TotalInstruction

When an instruction is executed in the debuggee, we receive an EXCEPTION_DEBUG_EVENT. We must examine the value of u.Exception.pExceptionRecord.ExceptionCode. If the value is EXCEPTION_SINGLE_STEP, then this debug event is generated because of the single-step mode. In this case, we can increment the variable TotalInstruction by one because we know that exactly one instruction was executed in the debuggee.

             invoke GetThreadContext,pi.hThread,addr context or context.regFlag,100h
             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE
             .continue

Since the trap flag is cleared after the debug exception is generated, we must set the trap flag again if we want to continue in single-step mode.
Warning: Don't use the example in this tutorial with a large program: tracing is SLOW. You may have to wait for ten minutes before you can close the debuggee.

[Iczelion's Win32 Assembly Homepage]


Iczelion's Win32 Assembly Tutorial 31: ListView Control

Tutorial 31: Listview Control

We will learn how to create and use the listview control in this tutorial.

Download the example.

Theory:

A listview control is one of the common controls like treeview, richedit etc. You are familiar with it even if you may not know it by its name. For example, the right pane of Windows Explorer is a listview control. A listview control is good for displaying items. In this regard, it's like a listbox but with enhanced capabilities.
You can create a listview control in two ways. The first method is also the easiest one: create it with a resource editor. Just don't forget to call InitCommonControls within your asm source code. The other method is to call CreateWindowEx in your source code. You must specify the correct window class name for the control, ie. SysListView32. The window class "WC_LISTVIEW" is incorrect.
There are four methods of viewing data in a listview control: icon, small icon, list and report views. You can see examples of these views by selecting View->Large Icons (icon view), Small Icons (small icon view), List (list view) and Details (report view). Views are just data representation methods:they only affect the appearances of data. For example, you can have a lot of data in the listview control, but if you want, you can view only some of them. Report view is the most informative one while the remaining views give less info. You can specify the view you want when you create a listview control. You can later change the view by calling SetWindowLong, specifying GWL_STYLE flag.

Now that we know how to create a listview control, we will continue on how to use it. I'll focus on report view which can demonstrate many features of listview control. The steps in using a listview control are as follows:

  1. Create a listview control with CreateWindowEx, specifying SysListView32 as the class name. You can specify the initial view at this time.
  2. (if exists) Create and initialize image lists to be used with the listview items.
  3. Insert column(s) into the listview control. This step is necessary if the listview control will use report view.
  4. Insert items and subitems into the listview control.

Columns

In report view, there are one or more columns. You can think of the arrangement of data in the report view as a table: the data are arranged in rows and columns. You must have at least one column in your listview control (only in report view). In views other than report, you need not insert a column because there can be one and only one column in those views.
You can insert a column by sending LVM_INSERTCOLUMN to the listview control.

LVM_INSERTCOLUMN
wParam = iCol
lParam = pointer to a LV_COLUMN structure

iCol is the column number, starting from 0.
LV_COLUMN contains information about the column to be inserted. It has the following definition:

LV_COLUMN STRUCT
  imask dd ?
  fmt dd ?
  lx dd ?
  pszText dd ?
  cchTextMax dd ?
  iSubItem dd ?
  iImage dd ?
  iOrder dd ?
LV_COLUMN ENDS

Field name | Meanings
imask

A collection of flags that governs which members in this structure are valid. The reason behind this member is that not all members in this structure are used at the same time. Some members are used in some situations. And this structure is used both for input and output. Thus it's important that you *mark* the members that are used in this call to Windows so Windows knows which members are valid. The available flags are:

LVCF_FMT = The fmt member is valid.
LVCF_SUBITEM = The iSubItem member is valid.
LVCF_TEXT = The pszText member is valid.
LVCF_WIDTH = The lx member is valid.

You can combine the above flags. For example, if you want to specify the text label of the column, you must supply the pointer to the string in pszText member. And you must tell Windows that pszText member contains data by specifying LVCF_TEXT flag in this field else Windows will ignore the value in pszText.

fmt

Specify the alignment of items/subitems in the column. The available values are:

LVCFMT_CENTER = Text is centered.
LVCFMT_LEFT = Text is left-aligned.
LVCFMT_RIGHT = Text is right-aligned.

lx | The width of the column, in pixels. You can later change the width of the column with LVM_SETCOLUMNWIDTH.
pszText | Contains a pointer to the name of the column if this structure is used to set the column's properties. If this structure is used to receive the properties of a column, this field contains a pointer to a buffer large enough to receive the name of the column that will be returned. In that case, you must give the size of the buffer in cchTextMax below. You can ignore cchTextMax if you want to set the name of the column because the name must be an ASCIIZ string which Windows can determine the length.
cchTextMax | The size, in bytes, of the buffer specified in pszText above. This member is used only when you use this structure to receive info about a column. If you use this structure to set the properties of a column, this field is ignored.
iSubItem | Specify the index of subitem associated with this column. This value is used as a marker which subitem this column is associated with. If you want, you can specify an absurd number in this field and your listview control will still run like a breeze. The use of this field is best demonstrated when you have the column number and need to know with which subitem this column is associated. You can query the listview control by sending LVM_GETCOLUMN message to it, specifying LVCF_SUBITEM in the imask member. The listview control will fill the iSubItem member with whatever value you specify in this field when the column is inserted. In order for this method to work, you need to input the correct subitem index into this field.
iImage and iOrder | Used with Internet Explorer 3.0 upwards. I don't have info about them.

So after the listview control is created, you should insert one or more columns into it. Columns are not necessary if you don't plan to switch the listview control into report view. In order to insert a column, you need to create a LV_COLUMN structure, fill it with necessary information, specify the column number and then send the structure to the listview control with LVM_INSERTCOLUMN message.

   LOCAL lvc:LV_COLUMN
   mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
   mov lvc.pszText,offset Heading1
   mov lvc.lx,150
   invoke SendMessage,hList, LVM_INSERTCOLUMN,0,addr lvc

The above code snippet demonstrates the process. It specifies the column header text and its width then send LVM_INSERTCOLUMN message to the listview control. It's that simple.

Items and subitems

Items are the main entries in the listview control. In views other than report view, you will only see items in the listview control. Subitems are details of the items. An item may have one or more associated subitems. For example, if the item is the name of a file, then you can have the file attributes, its size, the date of file creation as subitems. In report view, the leftmost column contains items and the remaining columns contain subitems. You can think of an item and its subitems as a database record. The item is the primary key of the record and the subitems are fields in the record.
At the bare minimum, you need some items in your listview control: subitems are not necessary. However, if you want to give the user more information about the items, you can associate items with subitems so the user can see the details in the report view.
You insert an item into a listview control by sending LVM_INSERTITEM message to it. You also need to pass the address of an LV_ITEM structure to it in lParam. LV_ITEM has the following definition:

LV_ITEM STRUCT
  imask dd ?
  iItem dd ?
  iSubItem dd ?
  state dd ?
  stateMask dd ?
  pszText dd ?
  cchTextMax dd ?
  iImage dd ?
  lParam dd ?
  iIndent dd ?
LV_ITEM ENDS

Field name | Meanings
imask | A collection of flags indicating which members in this structure are valid for this call. In general, this field is similar to imask member of LV_COLUMN above. Check your win32 api reference for more detail on the available flags.
iItem | The index of the item this structure refers to. The index is zero-based. You can think of this field as containing the "row" number of a table.
iSubItem | The index of the subitem associated with the item specified by iItem above. You can think of this field as containing the "column" of a table. For example, if you want to insert an item into a newly created listview control, the value in iItem would be 0 (because this item is the first one), and the value in iSubItem would also be 0 (we want to insert the item into the first column). If you want to specify a subitem associated with this item, the iItem would be the index of the item you want to associate with (in the above example, it's 0), the iSubItem would be 1 or greater, depending on which column you want to insert the subitem into. For example, if your listview control has 4 columns, the first column will contain the items. The remaining 3 columns are for subitems. If you want to insert a subitem into the 4th column, you need to specify the value 3 in iSubItem.
state | This member contains flags that reflect the status of the item. The state of an item can change because of the user's actions or it can be modified by our program. The state includes whether the item has the focus/is hilited/is selected for cut operation/is selected. In addition to the state flags, It can also contains one-based index into the overlay image/state image for use by the item.
stateMask | Since the state member above can contain the state flags, overlay image index , and state image index, we need to tell Windows which value we want to set or retrieve. The value in this field is for such use.
pszText | The address of an ASCIIZ string that will be used as the label of the item in the case we want to set/insert the item. In the case that we use this structure to retrieve the item's property, this member must contain the address of a buffer that will be filled with the label of the item.
cchTextMax | This field is used only when you use this structure to receive info about an item. In this case, this field contains the size in bytes of the buffer specified in pszText above.
iImage | The index into the imagelist containing the icons for the listview control. This index points to the icon to be used with this item.
lParam | A user-defined value that will be used when you sort items in the listview control. In short, when you tell the listview control to sort the items, the listview control will compare the items in pairs. It will send the lParam values of both items to you so you can decide which of the two should be listed first. If you're still hazy about this, don't worry. You'll learn more about sorting later.

Let's summarize the steps in inserting an item/subitem into a listview control.

  1. Create a variable of type LV_ITEM structure
  2. Fill it with necessary information
  3. Send LVM_INSERTITEM message to the listview control if you want to insert an item. Or if you want to *insert* a subitem, send LVM_SETITEM instead. This is rather confusing if you don't understand the relationship between an item and its subitems. Subitems are considered as properties of an item. Thus you can insert items but not subitems and you can't have a subitem without an associated item. That's why you need to send LVM_SETITEM message to add a subitem instead of LVM_INSERTITEM.

ListView Messages/Notifications

Now that you know how to create and populate a listview control, the next step is to communicate with it. A listview control communicates with the parent window via messages and notifications. The parent window can control the listview control by sending messages to it. The listview control notifies the parent of important/interesting events via WM_NOTIFY message, just like other common controls.

Sorting items/subitems

You can specify the default sorting order of a listview control by specifying LVS_SORTASCENDING or LVS_SORTDESCENDING styles in CreateWindowEx. These two styles order the items using their labels only. If you want to sort the items in other ways, you need to send LVM_SORTITEMS message to the listview control.

LVM_SORTITEMS
wParam = lParamSort
lParam = pCompareFunction

lParamSort is a user-defined value that will be passed to the compare function. You can use this value in any way you want.
pCompareFunction is the address of the user-defined function that will decide the outcome of the comparison of items in the listview control. The function has the following prototype:

CompareFunc proto lParam1:DWORD, lParam2:DWORD, lParamSort:DWORD

lParam1 and lParam2 are the values in lParam member of LV_ITEM that you specify when you insert the items into the listview control.
lParamSort is the value in wParam you sent with LVM_SORTITEMS

When the listview control receives LVM_SORTITEMS message, it calls the compare function specified in lParam of the message when it needs to ask us for the result of comparison between two items. In short, the comparison function will decide which of the two items sent to it will precede the other. The rule is simple: if the function returns a negative value, the first item (represented by lParam1) should precede the other. If the function returns a positive value, the second item (represented by lParam2) should precede the first one. If both items are equal, it must return zero.

What makes this method work is the value in lParam of LV_ITEM structure. If you need to sort the items (such as when the user clicks on a column header), you need to think of a sorting scheme that makes use of the values in lParam member. In the example, I put the index of the item in this field so I can obtain other information about the item by sending LVM_GETITEM message. Note that when the items are rearranged, their indexes also change. So when the sorting is done in my example, I need to update the values in lParam to reflect the new indexes. If you want to sort the items when the user clicks on a column header, you need to process LVN_COLUMNCLICK notification message in your window procedure. LVN_COLUMNCLICK is passed to your window proc via WM_NOTIFY message.

Example:

This example creates a listview control and fills it with the names and sizes of the files in the current folder. The default view is the report one. In the report view, you can click on the column heads and the items will be sorted in ascending/descending order. You can select the view you want via the menu. When you double-click on an item, a message box showing the label of the item is displayed.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

IDM_MAINMENU equ 10000
IDM_ICON equ LVS_ICON
IDM_SMALLICON equ LVS_SMALLICON
IDM_LIST equ LVS_LIST
IDM_REPORT equ LVS_REPORT

RGB macro red,green,blue
  xor eax,eax
  mov ah,blue
  shl eax,8
  mov ah,green
  mov al,red
endm

.data
ClassName db "ListViewWinClass",0
AppName db "Testing a ListView Control",0
ListViewClassName db "SysListView32",0
Heading1 db "Filename",0
Heading2 db "Size",0
FileNamePattern db "*.*",0
FileNameSortOrder dd 0
SizeSortOrder dd 0
template db "%lu",0

.data?
hInstance HINSTANCE ?
hList dd ?
hMenu dd ?

.code
start:
  invoke GetModuleHandle, NULL
  mov hInstance,eax
  invoke WinMain, hInstance,NULL, NULL, SW_SHOWDEFAULT
  invoke ExitProcess,eax
  invoke InitCommonControls
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
  LOCAL wc:WNDCLASSEX
  LOCAL msg:MSG
  LOCAL hwnd:HWND

  mov wc.cbSize,SIZEOF WNDCLASSEX
  mov wc.style, NULL
  mov wc.lpfnWndProc, OFFSET WndProc
  mov wc.cbClsExtra,NULL
  mov wc.cbWndExtra,NULL
  push hInstance
  pop wc.hInstance
  mov wc.hbrBackground,COLOR_WINDOW+1
  mov wc.lpszMenuName,IDM_MAINMENU
  mov wc.lpszClassName,OFFSET ClassName
  invoke LoadIcon,NULL,IDI_APPLICATION
  mov wc.hIcon,eax
  mov wc.hIconSm,eax
  invoke LoadCursor,NULL,IDC_ARROW
  mov wc.hCursor,eax
  invoke RegisterClassEx, addr wc
  invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInst,NULL
  mov hwnd,eax
  invoke ShowWindow, hwnd,SW_SHOWNORMAL
  invoke UpdateWindow, hwnd
  .while TRUE
    invoke GetMessage, ADDR msg,NULL,0,0
    .break .if (!eax)
      invoke TranslateMessage, ADDR msg
      invoke DispatchMessage, ADDR msg
  .endw
  mov eax,msg.wParam
  ret
WinMain endp

InsertColumn proc
  LOCAL lvc:LV_COLUMN

  mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
  mov lvc.pszText,offset Heading1
  mov lvc.lx,150
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc
  or lvc.imask,LVCF_FMT
  mov lvc.fmt,LVCFMT_RIGHT
  mov lvc.pszText,offset Heading2
  mov lvc.lx,100
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc
  ret
InsertColumn endp

ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD
  LOCAL lvi:LV_ITEM
  LOCAL buffer[20]:BYTE
  mov edi,lpFind
  assume edi:ptr WIN32_FIND_DATA
  mov lvi.imask,LVIF_TEXT+LVIF_PARAM
  push row
  pop lvi.iItem
  mov lvi.iSubItem,0
  lea eax,[edi].cFileName
  mov lvi.pszText,eax
  push row
  pop lvi.lParam
  invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi
  mov lvi.imask,LVIF_TEXT
  inc lvi.iSubItem
  invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow
  lea eax,buffer
  mov lvi.pszText,eax
  invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi
  assume edi:nothing
  ret
ShowFileInfo endp

FillFileInfo proc uses edi
  LOCAL finddata:WIN32_FIND_DATA
  LOCAL FHandle:DWORD

  invoke FindFirstFile,addr FileNamePattern,addr finddata
  .if eax!=INVALID_HANDLE_VALUE
    mov FHandle,eax
    xor edi,edi
    .while eax!=0
      test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY
      .if ZERO?
         invoke ShowFileInfo,edi, addr finddata
         inc edi
      .endif
      invoke FindNextFile,FHandle,addr finddata
    .endw
    invoke FindClose,FHandle
  .endif
  ret
FillFileInfo endp

String2Dword proc uses ecx edi edx esi String:DWORD
  LOCAL Result:DWORD

  mov Result,0
  mov edi,String
  invoke lstrlen,String
  .while eax!=0
    xor edx,edx
    mov dl,byte ptr [edi]
    sub dl,"0"
    mov esi,eax
    dec esi
    push eax
    mov eax,edx
    push ebx
    mov ebx,10
    .while esi > 0
      mul ebx
      dec esi
    .endw
    pop ebx
    add Result,eax
    pop eax
    inc edi
    dec eax
  .endw
  mov eax,Result
  ret
String2Dword endp

CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD
  LOCAL buffer[256]:BYTE
  LOCAL buffer1[256]:BYTE
  LOCAL lvi:LV_ITEM

  mov lvi.imask,LVIF_TEXT
  lea eax,buffer
  mov lvi.pszText,eax
  mov lvi.cchTextMax,256
  .if SortType==1
    mov lvi.iSubItem,1
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke String2Dword,addr buffer
    mov edi,eax
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke String2Dword,addr buffer
    sub edi,eax
    mov eax,edi
  .elseif SortType==2
    mov lvi.iSubItem,1
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke String2Dword,addr buffer
    mov edi,eax
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke String2Dword,addr buffer
    sub eax,edi
  .elseif SortType==3
    mov lvi.iSubItem,0
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke lstrcpy,addr buffer1,addr buffer
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke lstrcmpi,addr buffer1,addr buffer
  .else
    mov lvi.iSubItem,0
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke lstrcpy,addr buffer1,addr buffer
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke lstrcmpi,addr buffer,addr buffer1
  .endif
  ret
CompareFunc endp

UpdatelParam proc uses edi
   LOCAL lvi:LV_ITEM

   invoke SendMessage,hList, LVM_GETITEMCOUNT,0,0
   mov edi,eax
   mov lvi.imask,LVIF_PARAM
   mov lvi.iSubItem,0
   mov lvi.iItem,0
   .while edi>0
     push lvi.iItem
     pop lvi.lParam
     invoke SendMessage,hList, LVM_SETITEM,0,addr lvi
     inc lvi.iItem
     dec edi
   .endw
   ret
UpdatelParam endp

ShowCurrentFocus proc
   LOCAL lvi:LV_ITEM
   LOCAL buffer[256]:BYTE

   invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED
   mov lvi.iItem,eax
   mov lvi.iSubItem,0
   mov lvi.imask,LVIF_TEXT
   lea eax,buffer
   mov lvi.pszText,eax
   mov lvi.cchTextMax,256
   invoke SendMessage,hList,LVM_GETITEM,0,addr lvi
   invoke MessageBox,0, addr buffer,addr AppName,MB_OK
   ret
ShowCurrentFocus endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
  .if uMsg==WM_CREATE
    invoke CreateWindowEx, NULL, addr ListViewClassName, NULL, LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL
    mov hList, eax
    invoke InsertColumn
    invoke FillFileInfo
    RGB 255,255,255
    invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax
    invoke GetMenu,hWnd
    mov hMenu,eax
    invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED
  .elseif uMsg==WM_COMMAND
    .if lParam==0
      invoke GetWindowLong,hList,GWL_STYLE
      and eax,not LVS_TYPEMASK
      mov edx,wParam
      and edx,0FFFFh
      push edx
      or eax,edx
      invoke SetWindowLong,hList,GWL_STYLE,eax
      pop edx
      invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED
    .endif
  .elseif uMsg==WM_NOTIFY
    push edi
    mov edi,lParam
    assume edi:ptr NMHDR
    mov eax,[edi].hwndFrom
    .if eax==hList
      .if [edi].code==LVN_COLUMNCLICK
        assume edi:ptr NM_LISTVIEW
        .if [edi].iSubItem==1
          .if SizeSortOrder==0 || SizeSortOrder==2
            invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc
            invoke UpdatelParam
            mov SizeSortOrder,1
          .else
            invoke SendMessage,hList,LVM_SORTITEMS,2,addr CompareFunc
            invoke UpdatelParam
            mov SizeSortOrder,2
          .endif
        .else
          .if FileNameSortOrder==0 || FileNameSortOrder==4
            invoke SendMessage,hList,LVM_SORTITEMS,3,addr CompareFunc
            invoke UpdatelParam
            mov FileNameSortOrder,3
          .else
            invoke SendMessage,hList,LVM_SORTITEMS,4,addr CompareFunc
            invoke UpdatelParam
            mov FileNameSortOrder,4
          .endif
        .endif
        assume edi:ptr NMHDR
      .elseif [edi].code==NM_DBLCLK
        invoke ShowCurrentFocus
      .endif
    .endif
    pop edi
  .elseif uMsg==WM_SIZE
    mov eax,lParam
    mov edx,eax
    and eax,0ffffh
    shr edx,16
    invoke MoveWindow,hList, 0, 0, eax,edx,TRUE
  .elseif uMsg==WM_DESTROY
    invoke PostQuitMessage,NULL
  .else
    invoke DefWindowProc,hWnd,uMsg,wParam,lParam
    ret
  .endif
  xor eax,eax
  ret
WndProc endp
end start

Analysis:

The first thing the program does when the main window is created is to create a listview control.

  .if uMsg==WM_CREATE
    invoke CreateWindowEx, NULL, addr ListViewClassName, NULL, LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL
    mov hList, eax

We call CreateWindowEx, passing itthe name of the window class "SysListView32". The default view is the report view as specified by LVS_REPORT style.

    invoke InsertColumn

After the listview control is created, we insert columns into it.

  LOCAL lvc:LV_COLUMN

  mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
  mov lvc.pszText,offset Heading1
  mov lvc.lx,150
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc

We specify the label and the width of the first column, for storing the names of the files, in LV_COLUMN structure thus we need to set imask with LVCF_TEXT and LVCF_WIDTH flags. We fill pszText with the address of the label and lx with the width of the column, in pixels. When all is done, we send LVM_INSERTCOLUMN message to the listview control, passing the structure to it.

  or lvc.imask,LVCF_FMT
  mov lvc.fmt,LVCFMT_RIGHT

When we are done with the insertion of the first column, we insert another column for storing the sizes of the files. Since we need the sizes to right-align in the column, we need to specify a flag in fmt member, LVCFMT_RIGHT. We must also specify LVCF_FMT flag in imask, in addition to LVCF_TEXT and LVCF_WIDTH.

  mov lvc.pszText,offset Heading2
  mov lvc.lx,100
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc

The remaining code is simple. Put the address of the label in pszText and the width in lx. Then send LVM_INSERTCOLUMN message to the listview control, specifying the column number and the address of the structure.

When the columns are inserted, we can fill items in the listview control.

    invoke FillFileInfo

FillFileInfo has the following code.

FillFileInfo proc uses edi
  LOCAL finddata:WIN32_FIND_DATA
  LOCAL FHandle:DWORD

  invoke FindFirstFile,addr FileNamePattern,addr finddata

We call FindFirstFile to obtain the information of the first file that matches the search criteria. FindFirstFile has the following prototype:

FindFirstFile proto pFileName:DWORD, pWin32_Find_Data:DWORD

pFileName is the address of the filename to search for. This string can contain wildcards. In our example, we use *.*, which amounts to search for all the files in the current folder.
pWin32_Find_Data is the address of the WIN32_FIND_DATA structure that will be filled with information about the file (if found).

This function returns INVALID_HANDLE_VALUE in eax if no matching file is found. Otherwise it returns a search handle that will be used in subsequent FindNextFile calls.

  .if eax!=INVALID_HANDLE_VALUE
    mov FHandle,eax
    xor edi,edi

If a file is found, we store the search handle in a variable and then zero out edi which will be used as the index into the items (row number).

    .while eax!=0
      test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY
      .if ZERO?

In this tutorial, I don't want to deal with the folders yet so I filter them out by checking dwFileAttributes for files which have FILE_ATTRIBUTE_DIRECTORY flag set. If they are found, I skip to call FindNextFile.

          invoke ShowFileInfo,edi, addr finddata
          inc edi
      .endif
      invoke FindNextFile,FHandle,addr finddata
    .endw

We insert the name and size of the file into the listview control by calling ShowFileInfo function. Then we increase the current row number in edi. Lastly we proceed to call FindNextFile to search for the next file in the current folder until FindNextFile returns 0 (meaning no more file is found).

    invoke FindClose,FHandle
  .endif
  ret
FillFileInfo endp

When all files in the current folder are enumerated, we must close the search handle.

Now let's look at the ShowFileInfo function. This function accepts two parameters, the index of the item (row number) and the address of WIN32_FIND_DATA structure.

ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD
  LOCAL lvi:LV_ITEM
  LOCAL buffer[20]:BYTE
  mov edi,lpFind
  assume edi:ptr WIN32_FIND_DATA

Store the address of WIN32_FIND_DATA structure in edi.

  mov lvi.imask,LVIF_TEXT+LVIF_PARAM
  push row
  pop lvi.iItem
  mov lvi.iSubItem,0

We will supply the label of the item and the value in lParam so we put LVIF_TEXT and LVIF_PARAM flags into imask. Next we set the iItem to the row number passed to the function and since this is the main item, we must filliSubItem with 0 (column 0).

  lea eax,[edi].cFileName
  mov lvi.pszText,eax
  push row
  pop lvi.lParam

Next we put the address of the label, in this case, the name of the file in WIN32_FIND_DATA structure, into pszText. Because we will implement sorting in the listview control, we must fill lParam with a value. I choose to put the row number into this member so I can retrieve the item info by its index.

  invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi

When all necessary fields in LV_ITEM are filled, we send LVM_INSERTITEM message to the listview control to insert the item into it.

  mov lvi.imask,LVIF_TEXT
  inc lvi.iSubItem
  invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow
  lea eax,buffer
  mov lvi.pszText,eax

We will set the subitem associated with the item just inserted into the second column. A subitem can only have a label. Thus we specify LVIF_TEXT in imask. Then we specify the column that the subitem should reside in iSubItem. In this case, we set it to 1 by incrementing iSubItem. The label we will use is the size of the file. However, we must convert it to a string first by calling wsprintf. Then we put the address of the string into pszText.

  invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi
  assume edi:nothing
  ret
ShowFileInfo endp

When all necessary fields in LV_ITEM are filled, we send LVM_SETITEM message to the listview control, passing to it the address of the LV_ITEM structure. Note that we use LVM_SETITEM, not LVM_INSERTITEM because a subitem is considered as a property of an item. Thus we *set* the property of the item, not inserting a new item.

When all items are inserted into the listview control, we set the text and background colors of the listview control.

    RGB 255,255,255
    invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax

I use RGB macro to convert the red, green,blue values into eax and use it to specify the color we need. We set the foreground and background colors of the text with LVM_SETTEXTCOLOR and LVM_SETTEXTBKCOLOR messages. We set the background color of the listview control by sending LVM_SETBKCOLOR message to the listview control.

    invoke GetMenu,hWnd
    mov hMenu,eax
    invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED

We will let the user chooses the views he wants via the menu. Thus we must obtain the menu handle first. To help the user track the current view, we put a radio button system in our menu. The menu item that reflects the current view will be preceded by a radio button. That's why we call CheckMenuRadioItem. This function will put a radio button before a menu item.

Note that we create the listview control with width and height equal to 0. It will be resized later whenever the parent window is resized. This way, we can ensure that the size of the listview control will always match that of the parent window. In our example, we want the listview control to fill the whole client area of the parent window.

  .elseif uMsg==WM_SIZE
    mov eax,lParam
    mov edx,eax
    and eax,0ffffh
    shr edx,16
    invoke MoveWindow,hList, 0, 0, eax,edx,TRUE

When the parent window receives WM_SIZE message, the low word of lParam contains the new width of the client area and the high word the new height. Then we call MoveWindow to resize the listview control to cover the whole client area of the parent window.

When the user selects a view in the menu. We must change the view in the listview control accordingly. We accomplish this by setting a new style in the listview control with SetWindowLong.

  .elseif uMsg==WM_COMMAND
    .if lParam==0
      invoke GetWindowLong,hList,GWL_STYLE
      and eax,not LVS_TYPEMASK

The first thing we do is to obtain the current styles of the listview control. Then we clear the old view style from the returned style flags. LVS_TYPEMASK is a constant that is the combined value of all 4 view style constants (LVS_ICON+LVS_SMALLICON+LVS_LIST+LVS_REPORT). Thus when we perform and operation on the current style flags with the value "not LVS_TYPEMASK", it amounts to clearing away the current view style.

In designing the menu, I cheat a little. I use the view style constants as the menu IDs.

IDM_ICON equ LVS_ICON
IDM_SMALLICON equ LVS_SMALLICON
IDM_LIST equ LVS_LIST
IDM_REPORT equ LVS_REPORT

Thus when the parent window receives WM_COMMAND message, the desired view style is in the low word of wParam as the menu ID.

      mov edx,wParam
      and edx,0FFFFh

We have the desired view style in the low word of wParam. All we have to do is to zero out the high word.

      push edx
      or eax,edx

And add the desired view style to the existing styles (minus the current view style) of the listview control.

      invoke SetWindowLong,hList,GWL_STYLE,eax

And set the new styles with SetWindowLong.

      pop edx
      invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED
   .endif

We also need to put the radio button in front of the selected view menu item. Thus we call CheckMenuRadioItem, passing the current view style (double as menu ID) to it.

When the user clicks on the column headers in the report view, we want to sort the items in the listview control. We must respond to WM_NOTIFY message.

  .elseif uMsg==WM_NOTIFY
    push edi
    mov edi,lParam
    assume edi:ptr NMHDR
    mov eax,[edi].hwndFrom
    .if eax==hList

When we receive WM_NOTIFY message, lParam contains the pointer to an NMHDR structure. We can check if this message is from the listview control by comparing the hwndFrom member of NMHDR to the handle to the listview control. If they match, we can assume that the notification came from the listview control.

      .if [edi].code==LVN_COLUMNCLICK
        assume edi:ptr NM_LISTVIEW

If the notification is from the listview control, we check if the code is LVN_COLUMNCLICK. If it is, it means the user clicks on a column header. In the case that the code is LVN_COLUMNCLICK, we can assume that lParam contains the pointer to an NM_LISTVIEW structure which is a superset of the NMHDR structure. We then need to know on which column header the user clicks. Examination of iSubItem member reveals this info. The value in iSubItem can be treated as the column number, starting from 0.

        .if [edi].iSubItem==1
          .if SizeSortOrder==0 || SizeSortOrder==2

In the case iSubItem is 1, it means the user clicks on the second column, size. We use state variables to keep the current status of the sorting order. 0 means "no sorting yet", 1 means "sort ascending", 2 means "sort descending". If the items/subitems in the column are not sorted before, or sorted descending, we set the sorting order to ascending.

            invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc

We send LVM_SORTITEMS message to the listview control, passing 1 in wParam and the address of our comparison function in lParam. Note that the value in wParam is user-defined, you can use it in any way you like. I use it as the sorting method in this example. We will take a look at the comparison function first.

CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD
  LOCAL buffer[256]:BYTE
  LOCAL buffer1[256]:BYTE
  LOCAL lvi:LV_ITEM

  mov lvi.imask,LVIF_TEXT
  lea eax,buffer
  mov lvi.pszText,eax
  mov lvi.cchTextMax,256

In the comparison function, the listview control will pass lParams (in LV_ITEM) of the two items it needs to compare to us in lParam1 and lParam2. You'll recall that we put the index of the item in lParam. Thus we can obtain information about the items by querying the listview control using the indexes. The info we need is the labels of the items/subitems being sorted. Thus we prepare an LV_ITEM structure for such purpose, specifying LVIF_TEXT in imask and the address of the buffer in pszText and the size of the buffer in cchTextMax.

  .if SortType==1
    mov lvi.iSubItem,1
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi

If the value in SortType is 1 or 2, we know that the size column is clicked. 1 means sort the items according to their sizes in ascending order. 2 means the reverse. Thus we specify iSubItem as 1 ( to specify the size column) and send LVM_GETITEMTEXT message to the listview control to obtain the label (size string) of the subitem.

    invoke String2Dword,addr buffer
    mov edi,eax

Covert the size string into a dword value with String2Dword which is the function I wrote. It returns the dword value in eax. We store it in edi for comparison later.

    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke String2Dword,addr buffer
    sub edi,eax
    mov eax,edi

Do likewise with the value in lParam2. When we have the sizes of the two files, we can then compare them.
The rule of the comparison function is as follows:

  • If the first item should precede the other, you must return a negative value in eax
  • If the second item should precede the first one, you must return a positive value in eax
  • If both items are equal, you must return zero in eax.

In this case, we want to sort the items according to their sizes in ascending order. Thus we can simply subtract the size of the first item with that of the second one and return the result in eax.

  .elseif SortType==3
    mov lvi.iSubItem,0
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke lstrcpy,addr buffer1,addr buffer
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke lstrcmpi,addr buffer1,addr buffer

In case the user clicks the filename column, we must compare the names of the files. We first obtain the filenames and then compare them with lstrcmpi function. We can return the return value of lstrcmpi without any modification since it also uses the same rule of comparison, eg. negative value in eax if the first string is less than the second string.

When the items were sorted, we need to update the lParam values of all items to reflect the new indexes by calling UpdatelParam function.

            invoke UpdatelParam
            mov SizeSortOrder,1

This function simply enumerates all items in the listview control and updates the values in lParam with the new indexes. We need to do this else the next sort will not work as expected because our assumption is that the value in lParam is the index of the item.

      .elseif [edi].code==NM_DBLCLK
        invoke ShowCurrentFocus
      .endif

When the user double-clicks at an item, we want to display a message box with the label of the item on it. We must check if the code in NMHDR is NM_DBLCLK. If it is, we can proceed to obtain the label and display it in a message box.

ShowCurrentFocus proc
   LOCAL lvi:LV_ITEM
   LOCAL buffer[256]:BYTE

   invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED

How do we know which item is double-clicked? When an item is clicked or double-clicked, its state is set to "focused". Even if many items are hilited (selected), only one of them has got the focus. Our job than is to find the item that has the focus. We do this by sending LVM_GETNEXTITEM message to the listview control, specifying the desired state in lParam. -1 in wParam means search all items. The index of the item is returned in eax.

   mov lvi.iItem,eax
   mov lvi.iSubItem,0
   mov lvi.imask,LVIF_TEXT
   lea eax,buffer
   mov lvi.pszText,eax
   mov lvi.cchTextMax,256
   invoke SendMessage,hList,LVM_GETITEM,0,addr lvi

We then proceed to obtain the label by sending LVM_GETITEM message to the listview control.

   invoke MessageBox,0, addr buffer,addr AppName,MB_OK

Lastly, we display the label in a message box.

If you want to know how to use icons in the listview control, you can read about it in my treeview tutorial. The steps are just about the same.

[Iczelion's Win32 Assembly Homepage]


Iczelion's Win32 Assembly Tutorial 32: Multiple Document Interface (MDI)

Tutorial 32: Multiple Document Interface (MDI)

This tutorial shows you how to create MDI application. It's actually not too difficult to do. Download the example.

Theory:

Multiple Document Interface (MDI) is a specification for applications that handle multple documents at the same time. You are familiar with Notepad: It's an example of Single Document Interface (SDI). Notepad can handle only one document at a time. If you want to open another document, you have to close the previous one first. As you can imagine, it's rather cumbersome. Contrast it with Microsoft Word: Word can open arbitrary documents at the same time and let the user choose which document to use. Microsoft Word is an example of Multiple Document Interface (MDI).

MDI application has several characteristics that are distinctive. I'll list some of them:

  • Within the main window, there can be multiple child windows in the client area. All child windows are clipped to the client area.
  • When you minimize a child window, it minimizes to the lower left corner of the client area of the main window.
  • When you maximize achild window, its title merges with that of the main window.
  • You can close a child window by pressing Ctrl+F4 and switch the focus between the child windows by pressing Ctrl+Tab

The main window that contains the child windows is called a frame window. Its client area is where the child windows live, hence the name "frame". Its job is a little more elaborate than a usual window because it needs to handle some coordination for MDI.

To control an arbitrary number of child windows in your client area, you need a special window called client window. You can think of this client window as a transparent window that covers the whole client area of the frame window. It's this client window that is the actual parent of those MDI child windows. The client window is the real supervisor of the MDI child windows.

Frame Window
|
Client Window
|
|
|
|
|
|
MDI Child 1
MDI Child 2
MDI Child 3
MDI Child 4
MDI Child n

Figure 1. The hierachy of an MDI application

Creating the Frame Window

Now we can turn our attention to the detail. First of all you need to create a frame window. It's created the same way as the normal window: by calling CreateWindowEx. There are two major differences from a normal window.

The first difference is that you MUST call DefFrameProc instead of DefWindowProc to process the Windows messages your window don't want to handle. This is one way to let Windows do the dirty job of maintaining MDI application for you. If you forget to use DefFrameProc, your application won't get the MDI feature. Period. DefFrameProc has the following syntax:

DefFrameProc proc hwndFrame:DWORD,
                                   hwndClient:DWORD,
                                   uMsg:DWORD,
                                   wParam:DWORD,
                                   lParam:DWORD

If you compare DefFrameProc with DefWindowProc, you'll notice that the only difference between them is that DefFrameProc has 5 parameters while DefWindowProc has only 4. The extra parameter is the handle to the client window. This handle is necessary so Windows can send MDI-related messages to the client window.

The second difference is that, you must call TranslateMDISysAccel in the message loop of your frame window. This is necessary if you want Windows to handle MDI-related accelerator key strokes such as Ctrl+F4, Ctrl+Tab for you. It has the following syntax:

TranslateMDISysAccel proc hwndClient:DWORD,
                                                 lpMsg:DWORD

The first parameter is the handle to the client window. This should not come as a surprise to you because it's the client window that is the parent of all MDI child windows. The second parameter is the address of the MSG structure you filled by calling GetMessage. The idea is to pass the MSG structure to the client window so it could examine if the MSG structure contains the MDI-related keypresses. If so, it processes the message itself and returns a non-zero value, otherwise it returns FALSE.

The steps in creating the frame window can be summarized as follows:

  1. Fill in the WNDCLASSEX structure as usual
  2. Register the frame window class by calling RegisterClassEx
  3. Create the frame window by calling CreateWindowEx
  4. Within the message loop, call TranslateMDISysAccel.
  5. Within the window procedure, pass the unprocessed messages to DefFrameProc instead of DefWindowProc.

Creating the Client Window

Now that we have the frame window, we can create the client window. The client window class is pre-registered by Windows. The class name is "MDICLIENT". You also need to pass the address of a CLIENTCREATESTRUCT structure to CreateWindowEx. This structure has the following definition:

CLIENTCREATESTRUCT struct
        hWindowMenu    dd ?
        idFirstChild    dd ?
CLIENTCREATESTRUCT ends

hWindowMenu is the handle to the submenu that Windows will append the list of MDI child window names. This feature requires a little explanation. If you ever use an MDI application like Microsoft Word before, you'll notice that there is a submenu named "window" which, on activation, displays various menuitems related to window management and at the bottom, the list of the MDI child window currently opened. That list is internally maintained by Windows itself: you don't have to do anything special for it. Just pass the handle of the submenu you want the list to appear in hWindowMenu and Windows will handle the rest. Note that the submenu can be ANY submenu:it doesn't have to be the one that is named "window". The bottom line is that, you should pass the handle to the submenu you want the window list to appear. If you don't want the list, just put NULL in hWindowMenu. You get the handle to the submenu by calling GetSubMenu.

idFirstChild is the ID of the first MDI child window. Windows increments the ID for each new MDI child window the application created. For example, if you pass 100 to this field, the first MDI child window will have the ID of 100, the second one will have the ID of 101 and so on. This ID is sent to the frame window via WM_COMMAND when the MDI child window is selected from the window list. Normally you'll pass this "unhandled" WM_COMMAND messages to DefFrameProc. I use the word "unhandled" because the menuitems in the window list are not created by your application thus your application doesn't know their IDs and doesn't have the handler for them. This is another special case for the MDI frame window: if you have the window list, you must modify your WM_COMMAND handler a bit like this:

.elseif uMsg==WM_COMMAND
      .if lParam==0			; this message is generated from a menu
          mov eax,wParam
          .if ax==IDM_CASCADE
                 .....
          .elseif ax==IDM_TILEVERT
                .....
          .else
                invoke DefFrameProc, hwndFrame, hwndClient, uMsg,wParam, lParam
                ret
          .endif

Normally, you would just ignore the messages from unhandled cases. But In the MDI case, if you ignore them, when the user clicks on the name of an MDI child window in the window list, that window won't become active. You need to pass them to DefFrameProc so they can be handled properly.

A caution on the value of idFirstChild: you should not use 0. Your window list will not behave properly, ie. the check mark will not appear in front of the name of the first MDI child even though it's active. Choose a safe value such as 100 or above.

Having filled in the CLIENTCREATESTRUCT structure, you can create the client window by calling CreateWindowEx with the predefined class name,"MDICLIENT", and passing the address of the CLIENTCREATESTRUCT structure in lParam. You must also specify the handle to the frame window in the hWndParent parameter so Windows knows the parent-child relationship between the frame window and the client window. The window styles you should use are: WS_CHILD ,WS_VISIBLE and WS_CLIPCHILDREN. If you forget WS_VISIBLE, you won't see the MDI child windows even if they were created successfully.

The steps in creating the client window are as follows:

  1. Obtain the handle to the submenu that you want to append the window list to.
  2. Put the value of the menu handle along with the value you want to use as the ID of the first MDI child window in a CLIENTCREATESTRUCT structure
  3. call CreateWindowEx with the class name "MDICLIENT", passing the address of the CLIENTCREATESTRUCT structure you just filled in in lParam.

Creating the MDI Child Window

Now you have both the frame window and the client window. The stage is now ready for the creation of the MDI child window. There are two ways to do that.

  • You can send WM_MDICREATE message to the client window, passing the address of a structure of type MDICREATESTRUCT in wParam. This is the easiest and the usual method of MDI child window creation.
    .data?
        mdicreate MDICREATESTRUCT <>
        ....
    .code
        .....
        [fill the members of mdicreate]
        ......
    invoke SendMessage, hwndClient, WM_MDICREATE,addr mdicreate,0

    SendMessage will return the handle of the newly created MDI child window if successful. You don't need to save the handle though. You can obtain it by other means if you want to. MDICREATESTRUCT has the following definition.

    • MDICREATESTRUCT STRUCT
    szClass DWORD ?
    szTitle DWORD ?
    hOwner DWORD ?
    x DWORD ?
    y DWORD ?
    lx DWORD ?
    ly DWORD ?
    style DWORD ?
    lParam DWORD ?
    MDICREATESTRUCT ENDS
szClass | the address of the window class you want to use as the template for the MDI child window.
szTitle | the address of the text you want to appear in the title bar of the child window
hOwner | the instance handle of the application
x,y,lx,ly | the upper left coordinate and the width and height of the child window
style | child window style. If you create the client window with MDIS_ALLCHILDSTYLES, you can use any window style.
lParam | an application-defined 32-bit value. This is a way of sharing values among MDI windows. If you don't need to use it, set it to NULL
  • You can call CreateMDIWindow. This function has the following syntax:
    CreateMDIWindow proto lpClassName:DWORD
                                               lpWindowName:DWORD
                                               dwStyle:DWORD
                                           x:DWORD
                                               y:DWORD
                                               nWidth:DWORD
                                               nHeight:DWORD
                                               hWndParent:DWORD
                                               hInstance:DWORD
                                               lParam:DWORD

If you look closely at the parameters, you'll find that they are identical to the members of MDICREATESTRUCT structure, except for the hWndParent. Essentially it's the same number of parameters you pass with WM_MDICREATE. MDICREATESTRUCT doesn't have the hWndParent field because you must pass the whole structure to the correct client window with SendMessage anyway.

At this point, you may have some questions: which method should I use? What is the difference between the two? Here is the answer:

The WM_MDICREATE method can only create the MDI child window in the same thread as the calling code. For example, if your application has 2 threads, and the first thread creates the MDI frame window, if the second thread wants to create an MDI child, it must do so with CreateMDIChild: sending WM_MDICREATE message to the first thread won't work. If your application is single-threaded, you can use either method. (Thanks yap for the correction - 04/24/2002)

A little more detail needs to be covered about the window procedure of the MDI child. As with the frame window case, you must not call DefWindowProc to handle the unprocessed messages. Instead, you must use DefMDIChildProc. This function has exactly the same parameters as DefWindowProc.

In addition to WM_MDICREATE, there are other MDI-related window messages. I'll list them below:

WM_MDIACTIVATE | This message can be sent by the application to the client window to instruct the client window to activate the selected MDI child. When the client window receives the message, it activates the selected MDI child window and sends WM_MDIACTIVATE to the child being deactivated and activated. The use of this message is two-fold: it can be used by the application to activate the desired child window. And it can be used by the MDI child window itself as the indicator that it's being activated/deactivated. For example, if each MDI child window has different menu, it can use this opportunity to change the menu of the frame window when it's activated/deactivated.
WM_MDICASCADE WM_MDITILE WM_MDIICONARRANGE | These messages handle the arrangement of the MDI child windows. For example, if you want the MDI child windows to arrange themselves in cascading style, send WM_MDICASCADE to the client window.
WM_MDIDESTROY | Send this message to the client window to destroy an MDI child window. You should use this message instead of calling DestroyWindow because if the MDI child window is maxmized, this message will restore the tile of the frame window. If you use DestroyWindow, the title of the frame window will not be restored.
WM_MDIGETACTIVE | Send this message to retrieve the handle of the currently active MDI child window.
WM_MDIMAXIMIZE WM_MDIRESTORE | Send WM_MDIMAXIMIZE to maximize the MDI child window and WM_MDIRESTORE to restore it to previous state. Always use these messages for the operations. If you use ShowWindow with SW_MAXIMIZE, the MDI child window will maximize fine but it will have the problem when you try to restore it to previous size. You can minimize the MDI child window with ShowWindow without problem, however.
WM_MDINEXT | Send this message to the client window to activate the next or the previous MDI child window according to the values in wParam and lParam.
WM_MDIREFRESHMENU | Send this message to the client window to refresh the menu of the frame window. Note that you must call DrawMenuBar to update the menu bar after sending this message.
WM_MDISETMENU | Send this message to the client window to replace the whole menu of the frame window or just the window submenu. You must use this message instead of SetMenu. After sending this message, you must call DrawMenuBar to update the menu bar. Normally you will use this message when the active MDI child window has its own menu and you want it to replace the menu of the frame window while the MDI child window is active.

I'll review the steps in creating an MDI application for you again below.

  1. Register the window classes, both the frame window class and the MDI child window class
  2. Create the frame window with CreateWindowEx.
  3. Within the message loop, call TranslateMDISysAccel to process the MDI-related accelerator keys
  4. Within the window procedure of the frame window, call DefFrameProc to handle ALL messages unhandled by your code.
  5. Create the client window by calling CreateWindowEx using the name of the predefined window class, "MDICLIENT", passing the address of a CLIENTCREATESTRUCT structure in lParam. Normally, you would create the client window within the WM_CREATE handler of the frame window proc
  6. You can create an MDI child window by sending WM_MDICREATE to the client window or, alternatively, by calling CreateMDIWindow.
  7. Within the window proc of the MDI child window, pass all unhandled messages to DefMDIChildProc.
  8. Use MDI version of the messages if it exists. For example, use WM_MDIDESTROY instead of calling DestroyWindow

Example:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.const
IDR_MAINMENU 	equ 101
IDR_CHILDMENU	equ 102
IDM_EXIT 		equ 40001
IDM_TILEHORZ	equ 40002
IDM_TILEVERT	equ 40003
IDM_CASCADE	equ 40004
IDM_NEW 		equ 40005
IDM_CLOSE	equ 40006

.data
ClassName 	db "MDIASMClass",0
MDIClientName	db "MDICLIENT",0
MDIChildClassName	db "Win32asmMDIChild",0
MDIChildTitle	db "MDI Child",0
AppName		db "Win32asm MDI Demo",0
ClosePromptMessage	db "Are you sure you want to close this window?",0

.data?
hInstance 	dd ?
hMainMenu 	dd ?
hwndClient 	dd ?
hChildMenu 	dd ?
mdicreate		MDICREATESTRUCT <>
hwndFrame 	dd ?

.code
start:
    invoke GetModuleHandle, NULL
    mov hInstance,eax
    invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
    invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    ;=============================================
    ; Register the frame window class
    ;=============================================
    mov wc.cbSize,SIZEOF WNDCLASSEX
    mov wc.style, CS_HREDRAW or CS_VREDRAW
    mov wc.lpfnWndProc,OFFSET WndProc
    mov wc.cbClsExtra,NULL
    mov wc.cbWndExtra,NULL
    push hInstance
    pop wc.hInstance
    mov wc.hbrBackground,COLOR_APPWORKSPACE
    mov wc.lpszMenuName,IDR_MAINMENU
    mov wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov wc.hIcon,eax
    mov wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    ;================================================
    ; Register the MDI child window class
    ;================================================
    mov wc.lpfnWndProc,offset ChildProc
    mov wc.hbrBackground,COLOR_WINDOW+1
    mov wc.lpszClassName,offset MDIChildClassName
    invoke RegisterClassEx,addr wc
    invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
            WS_OVERLAPPEDWINDOW or WS_CLIPCHILDREN,CW_USEDEFAULT,\
            CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,0,\
            hInst,NULL
    mov hwndFrame,eax
    invoke LoadMenu,hInstance, IDR_CHILDMENU
    mov hChildMenu,eax
    invoke ShowWindow,hwndFrame,SW_SHOWNORMAL
    invoke UpdateWindow, hwndFrame
    .while TRUE
        invoke GetMessage,ADDR msg,NULL,0,0
        .break .if (!eax)
        invoke TranslateMDISysAccel,hwndClient,addr msg
        .if !eax
            invoke TranslateMessage, ADDR msg
            invoke DispatchMessage, ADDR msg
        .endif
    .endw
    invoke DestroyMenu, hChildMenu
    mov eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    LOCAL ClientStruct:CLIENTCREATESTRUCT
    .if uMsg==WM_CREATE
        invoke GetMenu,hWnd
        mov hMainMenu,eax
        invoke GetSubMenu,hMainMenu,1
        mov ClientStruct.hWindowMenu,eax
        mov ClientStruct.idFirstChild,100
        INVOKE CreateWindowEx,NULL,ADDR MDIClientName,NULL,\
                WS_CHILD or WS_VISIBLE or WS_CLIPCHILDREN,CW_USEDEFAULT,\
                CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,\
                hInstance,addr ClientStruct
        mov hwndClient,eax
        ;=======================================
        ; Initialize the MDICREATESTRUCT
        ;=======================================
        mov mdicreate.szClass,offset MDIChildClassName
        mov mdicreate.szTitle,offset MDIChildTitle
        push hInstance
        pop mdicreate.hOwner
        mov mdicreate.x,CW_USEDEFAULT
        mov mdicreate.y,CW_USEDEFAULT
        mov mdicreate.lx,CW_USEDEFAULT
        mov mdicreate.ly,CW_USEDEFAULT
    .elseif uMsg==WM_COMMAND
        .if lParam==0
            mov eax,wParam
            .if ax==IDM_EXIT
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDM_TILEHORZ
                invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0
            .elseif ax==IDM_TILEVERT
                invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_VERTICAL,0
            .elseif ax==IDM_CASCADE
                invoke SendMessage,hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0
            .elseif ax==IDM_NEW
                invoke SendMessage,hwndClient,WM_MDICREATE,0,addr mdicreate
            .elseif ax==IDM_CLOSE
                invoke SendMessage,hwndClient,WM_MDIGETACTIVE,0,0
                invoke SendMessage,eax,WM_CLOSE,0,0
            .else
                invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam
                ret
            .endif
        .endif
    .elseif uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .else
        invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
WndProc endp

ChildProc proc hChild:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
    .if uMsg==WM_MDIACTIVATE
        mov eax,lParam
        .if eax==hChild
            invoke GetSubMenu,hChildMenu,1
            mov edx,eax
            invoke SendMessage,hwndClient,WM_MDISETMENU,hChildMenu,edx
        .else
            invoke GetSubMenu,hMainMenu,1
            mov edx,eax
            invoke SendMessage,hwndClient,WM_MDISETMENU,hMainMenu,edx
        .endif
        invoke DrawMenuBar,hwndFrame
    .elseif uMsg==WM_CLOSE
        invoke MessageBox,hChild,addr ClosePromptMessage,addr AppName,MB_YESNO
        .if eax==IDYES
            invoke SendMessage,hwndClient,WM_MDIDESTROY,hChild,0
        .endif
    .else
        invoke DefMDIChildProc,hChild,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
ChildProc endp
end start

Analysis:

The first thing the program does is to register the window classes of the frame window and the MDI child window. After that, it calls CreateWindowEx to create the frame window. Within the WM_CREATE handler of the frame window, we create the client window:

    LOCAL ClientStruct:CLIENTCREATESTRUCT
    .if uMsg==WM_CREATE
        invoke GetMenu,hWnd
        mov hMainMenu,eax
        invoke GetSubMenu,hMainMenu,1
        mov ClientStruct.hWindowMenu,eax
        mov ClientStruct.idFirstChild,100
        invoke CreateWindowEx,NULL,ADDR MDIClientName,NULL,\
            WS_CHILD or WS_VISIBLE or WS_CLIPCHILDREN,CW_USEDEFAULT,\
            CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,\
            hInstance,addr ClientStruct
        mov hwndClient,eax

It calls GetMenu to obtain the handle to the menu of the frame window, to be used in the GetSubMenu call. Note that we pass the value 1 to GetSubMenu because the submenu we want the window list to appear is the second submenu. Then we fill the members of the CLIENTCREATESTRUCT structure.
Next, we initialize the MDICLIENTSTRUCT structure. Note that we don't need to do it here. It's only convenient to do it in WM_CREATE.

    mov mdicreate.szClass,offset MDIChildClassName
    mov mdicreate.szTitle,offset MDIChildTitle
    push hInstance
    pop mdicreate.hOwner
    mov mdicreate.x,CW_USEDEFAULT
    mov mdicreate.y,CW_USEDEFAULT
    mov mdicreate.lx,CW_USEDEFAULT
    mov mdicreate.ly,CW_USEDEFAULT

After the frame window is created (and also the client window), we call LoadMenu to load the child window menu from the resource. We need to get this menu handle so we can replace the menu of the frame window with it when an MDI child window is present. Don't forget to call DestroyMenu on the handle before the application exits to Windows. Normally Windows will free the menu associated with a window automatically when the application exits but in this case, the child window menu is not associated with any window thus it will still occupy valuable memory even after the application exits.

    invoke LoadMenu,hInstance, IDR_CHILDMENU
    mov hChildMenu,eax
    ........
    invoke DestroyMenu, hChildMenu

Within the message loop, we call TranslateMDISysAccel.

    .while TRUE
        invoke GetMessage,ADDR msg,NULL,0,0
        .break .if (!eax)
        invoke TranslateMDISysAccel,hwndClient,addr msg
        .if !eax
            invoke TranslateMessage, ADDR msg
            invoke DispatchMessage, ADDR msg
        .endif
    .endw

If TranslateMDISysAccel returns a non-zero value, it means the message was already handled by Windows itself so you don't need to do anything to the message. If it returns 0, the message is not MDI-related and thus should be handled as usual.

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .....
    .else
        invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
WndProc endp

Note that within the window procedure of the frame window, we call DefFrameProc to handle the messages we are not interested in.

The bulk of the window procedure is the WM_COMMAND handler. When the user selects "New" from the File menu, we create a new MDI child window.

    .elseif ax==IDM_NEW
        invoke SendMessage,hwndClient,WM_MDICREATE,0,addr mdicreate

In our example, we create the MDI child window by sending WM_MDICREATE to the client window, passing the address of the MDICREATESTRUCT structure in lParam.

ChildProc proc hChild:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
    .if uMsg==WM_MDIACTIVATE
        mov eax,lParam
        .if eax==hChild
            invoke GetSubMenu,hChildMenu,1
            mov edx,eax
            invoke SendMessage,hwndClient,WM_MDISETMENU,hChildMenu,edx
        .else
            invoke GetSubMenu,hMainMenu,1
            mov edx,eax
            invoke SendMessage,hwndClient,WM_MDISETMENU,hMainMenu,edx
        .endif
        invoke DrawMenuBar,hwndFrame

When the MDI child window is created, it monitors WM_MDIACTIVATE to see if it's the active window. It does this by comparing the value of the lParam which contains the handle of the active child window with its own handle. If they match, it's the active window and the next step is to replace the menu of the frame window to its own. Since the original menu will be replaced, you have to tell Windows again in which submenu the window list should appear. That's why we must call GetSubMenu again to retrieve the handle to the submenu. We send WM_MDISETMENU message to the client window to achieve the desired result. wParam of WM_MDISETMENU contains the handle of the menu you would like to replace the original menu. lParam contains the handle of the submenu you want the window list to appear. Right after sending WM_MDISETMENU, we call DrawMenuBar to refresh the menu else your menu will be a mess.

    .else
        invoke DefMDIChildProc,hChild,uMsg,wParam,lParam
        ret
    .endif
Within the window procedure of the MDI    child window, you must pass all unhandled messages to DefMDIChildProc    instead of DefWindowProc.
    .elseif ax==IDM_TILEHORZ
        invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0
    .elseif ax==IDM_TILEVERT
        invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_VERTICAL,0
    .elseif ax==IDM_CASCADE
        invoke SendMessage,hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0

When the user selects one of the menuitems in the window submenu, we send the corresponding message to the client window. If the user chooses to tile the windows, we send WM_MDITILE to the client window, specifying in wParam what kind of tiling we want. WM_CASCADE is similar.

    .elseif ax==IDM_CLOSE
        invoke SendMessage,hwndClient,WM_MDIGETACTIVE,0,0
        invoke SendMessage,eax,WM_CLOSE,0,0
If the user chooses "Close"    menuitem, we must obtain the handle of the currently active MDI child window    first by sending WM_MDIGETACTIVE to the client window. The return value in eax    is the handle of the currently active MDI child window. After that, we send    WM_CLOSE to that window.
    .elseif uMsg==WM_CLOSE
        invoke MessageBox,hChild,addr ClosePromptMessage,addr AppName,MB_YESNO
        .if eax==IDYES
            invoke SendMessage,hwndClient,WM_MDIDESTROY,hChild,0
        .endif

Within the window procedure of the MDI child, when WM_CLOSE is received, it displays a message box asking the user if he really wants to close the window. If the answer is yes, we send WM_MDIDESTROY to the client window. WM_MDIDESTROY closes the MDI child window and restores the title of the frame window.

[Iczelion's Win32 Assembly Homepage]


Iczelion's Win32 Assembly Tutorial 33: RichEdit Control: Basics

Tutorial 33: RichEdit Control: Basics

There are lots of request on tutorials about RichEdit controls. Finally I have played with it enough to think I can write tutorials about it. So here it is: the first RichEdit tutorial. The tutorials will describe nearly everything there is to know about RichEdit control or at least as much as I know it. The amount of information is rather large so I divide it into several parts, this tutorial being the first part. In this tutorial, you'll learn what a RichEdit control is, how to create it and how to load/save data to/from it.

Download the example.

Theory

A richedit control can be thought of as a souped-up edit control. It provides many desirable features that are lacking from the plain simple edit control, for example, the ability to use multiple font face/size, multiple-level undo/redo, search-for-text operation, OLE-embedded objects, drag-and-drop editing support, etc. Since the richedit control has so many features, it's stored in a separate DLL. This also means that, to use it, you can't just call InitCommonControls like other common controls. You have to call LoadLibrary to load the richedit DLL.

The problem is that there are three versions of richedit control up till now. Version 1,2, and 3. The table below shows you the name of the DLL for each version.

DLL Name | RichEdit version | Richedit Class Name
Riched32.dll | 1.0 | RICHEDIT
RichEd20.dll | 2.0 | RICHEDIT20A
RichEd20.dll | 3.0 | RICHEDIT20A

You can notice that richedit version 2 and 3 use the same DLL name. They also use the same class name! This can pose a problem if you want to use specific features of richedit 3.0. Up to now, I haven't found an official method of differentiating between version 2.0 and 3.0. However, there is a workaround which works ok, I'll show you later.

.data
   RichEditDLL db "RichEd20.dll",0
    .....
.data?
    hRichEditDLL dd ?
.code
    invoke LoadLibrary,addr RichEditDLL
    mov hRichEditDLL,eax
    ......
    invoke FreeLibrary,hRichEditDLL

When the richedit dll is loaded, it registers the RichEdit window class. Thus it's imperative that you load the DLL before you create the control. The names of the richedit control classes are also different. Now you may have a question: how do I know which version of richedit control should I use? Using the latest version is not always appropriate if you don't require the extra features. So below is the table that shows the features provided by each version of richedit control.

Feature | Version 1.0 | Version 2.0 | Version 3.0
selection bar | x | x | x
unicode editing |  | x | x
character/paragraph formatting | x | x | x
search for text | forward | forward/backward | forward/backward
OLE embedding | x | x | x
Drag and drop editing | x | x | x
Undo/Redo | single-level | multi-level | multi-level
automatic URL recognition |  | x | x
Accelerator key support |  | x | x
Windowless operation |  | x | x
Line break | CRLF | CR only | CR only (can emulate version 1.0)
Zoom |  |  | x
Paragraph numbering |  |  | x
simple table |  |  | x
normal and heading styles |  |  | x
underline coloring |  |  | x
hidden text |  |  | x
font binding |  |  | x

The above table is by no means comprehensive: I only list the important features.

Creating the richedit control

After loading the richedit dll, you can call CreateWindowEx to create the control. You can use edit control styles and common window styles in CreateWindowEx except ES_LOWERCASE, ES_UPPERCASE and ES_OEMCONVERT.

.const
    RichEditID equ 300
.data
    RichEditDLL db "RichEd20.dll",0
    RichEditClass db "RichEdit20A",0
    ...
.data?
    hRichEditDLL dd ?
    hwndRichEdit dd ?
.code
    .....
    invoke LoadLibrary,addr RichEditDLL
    mov hRichEditDLL,eax
    invoke CreateWindowEx,0,addr RichEditClass,WS_VISIBLE or ES_MULTILINE or WS_CHILD or WS_VSCROLL or WS_HSCROLL, \
                                        CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,RichEditID,hInstance,0
    mov hwndRichEdit,eax

Setting default text and background color

You may have the problem with setting text color and the background color of the edit control. But this problem has been remedy in richedit control. To set the background color of the richedit control, you send EM_SETBKGNDCOLOR to the richedit control. This message has the following syntax.

wParam == color option. The value of 0 in this parameter specifies that Windows uses the color value in lParam as the background color. If this value is nonzero, Windows uses the Windows system background color. Since we send this message to change the background color, we must pass 0 in wParam.
lParam == specifies the COLORREF structure of the color you want to set if wParam is 0.

For example, if I want to set the background color to pure blue, I would issue this following line:

        invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,0FF0000h

To set the text color, richedit control provides another new message, EM_SETCHARFORMAT, for the job. This message controls the text formatting of a range to characters in selection or all text in the control. This message has the following syntax:

wParam == formatting options:

SCF_ALL | The operation affects all text in the control.
SCF_SELECTION | The operation affects only the text in selection
SCF_WORD or SCF_SELECTION | Affects the word in selection. If the selection is empy, ie, only the caret is in the word, the operation affects that word. SCF_WORD flag must be used with SCF_SELECTION.

lParam == pointer to a CHARFORMAT or CHARFORMAT2 structure that specifies the text formatting to be applied. CHARFORMAT2 is available for richedit 2.0 and above only. This doesn't mean that you must use CHARFORMAT2 with RichEdit 2.0 or above. You can still use CHARFORMAT if the added features in CHARFORMAT2 are not necessary for your need.

CHARFORMATA STRUCT
    cbSize DWORD ?
    dwMask DWORD ?
    dwEffects DWORD    ?
    yHeight DWORD ?
    yOffset DWORD ?
    crTextColor COLORREF ?
    bCharSet BYTE ?
    bPitchAndFamily    BYTE ?
    szFaceName BYTE LF_FACESIZE dup(?)
    _wPad2 WORD ?
CHARFORMATA ENDS
Field Name | Description
cbSize | The size of the structure. RichEdit control uses this field to determine the version of the structure whether it is CHARFORMAT or CHARFORMAT2
dwMask

Bit flags that determine which of the following members are valid.

CFM_BOLD | The CFE_BOLD value of the dwEffects member is valid
CFM_CHARSET | The bCharSet member is valid.
CFM_COLOR | The crTextColor member and the CFE_AUTOCOLOR value of the dwEffects member are valid
CFM_FACE | The szFaceName member is valid.
CFM_ITALIC | The CFE_ITALIC value of the dwEffects member is valid
CFM_OFFSET | The yOffset member is valid
CFM_PROTECTED | The CFE_PROTECTED value of the dwEffects member is valid
CFM_SIZE | The yHeight member is valid
CFM_STRIKEOUT | The CFE_STRIKEOUT value of the dwEffects member is valid.
CFM_UNDERLINE | The CFE_UNDERLINE value of the dwEffects member is valid
dwEffects

The character effects. Can be the combination of the following values

CFE_AUTOCOLOR | Use the system text color
CFE_BOLD | Characters are bold
CFE_ITALIC | Characters are italic
CFE_STRIKEOUT | Characters are struck.
CFE_UNDERLINE | Characters are underlined
CFE_PROTECTED | Characters are protected; an attempt to modify them will cause an EN_PROTECTED notification message.
yHeight | Character height, in twips (1/1440 of an inch or 1/20 of a printer's point).
yOffset | Character offset, in twips, from the baseline. If the value of this member is positive, the character is a superscript; if it is negative, the character is a subscript.
crTextColor | Text color. This member is ignored if the CFE_AUTOCOLOR character effect is specified.
bCharSet | Character set value
bPitchAndFamily | Font family and pitch.
szFaceName | Null-terminated character array specifying the font name
_wPad2 | Padding

From examination of the structure, you'll see that we can change the text effects (bold,italic, strikeout,underline), text color (crTextColor) and font face/size/character set. A notable flag is CFE_RPOTECTED. The text with this flag is marked as protected which means that when the user tries to modify it, EN_PROTECTED notification message will be sent to the parent window. And you can allow the change to happen or not.

CHARFORMAT2 adds more text styles such as font weight, spacing,text background color, kerning, etc. If you don't need these extra features, simply use CHARFORMAT.

To set text formatting, you have to think about the range of text you want to apply to. Richedit control introduces the notion of character text range. Richedit control gives each character a number starting from 0: the first characterin the control has Id of 0, the second character 1 and so on. To specify a text range, you must give the richedit control two numbers: the IDs of the first and the last character of the range. To apply the text formatting with EM_SETCHARFORMAT, you have at most three choices:

  1. Apply to all text in the control (SCF_ALL)
  2. Apply to the text currently in selection (SCF_SELECTION)
  3. Apply to the whole word currently in selection (SCF_WORD or SCF_SELECTION)

The first and the second choices are straightforward. The last choice requires a little explanation. If the current selection only covers one or more of the characters in the word but not the whole word, specifying the flag SCF_WORD+SCF_SELECTION applies the text formatting to the whole word. Even if there is no current selection, ie, only the caret is positioned in the word, the third choice also applies the text formatting to the whole word.

To use EM_SETCHARFORMAT, you need to fill several members of CHARFORMAT (or CHARFORMAT2) structure. For example, if we want to set the text color, we will fill the CHARFORMAT structure as follows:

.data?
    cf CHARFORMAT <>
....
.code
    mov cf.cbSize,sizeof cf
    mov cf.dwMask,CFM_COLOR
    mov cf.crTextColor,0FF0000h
    invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cf

The above code snippet sets the text color of the richedit control to pure blue. Note that if there is no text in the richedit control when EM_SETCHARFORMAT is issued, the text entered into the richedit control following the message will use the text formatting specified by the EM_SETCHARFORMAT message.

Setting the text/saving the text

For those of you who are used to edit control, you'll surely be familiar with WM_GETTEXT/WM_SETTEXT as the means to set the text/get the text to/from the control. This method still works with richedit control but may not be efficient if the file is large. Edit control limits the text that can be entered into it to 64K but richedit control can accept text much larger than that. It would be very cumbersome to allocate a very large block of memory (such as 10 MB or so) to receive the text from WM_GETTEXT. Richedit control offers a new approach to this method, ie. text streaming.

To put it simply, you provide the address of a callback function to the richedit control. And richedit control will call that callback, passing the address of the buffer to it, when it's ready. The callback will fill the buffer with the data it wants to send to the control or read the data from the buffer and then waits for the next call until the operation is finished. This paradigm is used for both streaming in (setting the text) and streaming out (getting the text out of the control). You'll see that this method is more efficient: the buffer is provided by the richedit control itself so the data are divided into chunks. The operations involve two messages: EM_STREAMIN and EM_STREAMOUT

Both EM_STREAMIN and EM_STREAMOUT use the same syntax:

wParam == formatting options.

SF_RTF | The data is in the rich-text format (RTF)
SF_TEXT | The data is in the plain text format
SFF_PLAINRTF | Only the keywords common to all languages are streamed in.
SFF_SELECTION | If specified, the target of the operation is the text currently in selection. If you stream the text in, the text replaces the current selection. If you stream the text out, only the text currently in selection is streamed out. If this flag is not specified, the operation affects the whole text in the control.
SF_UNICODE | (Available on RichEdit 2.0 or later) Specify the unicode text.

lParam == point to an EDITSTREAM structure which has the following definition:

 EDITSTREAM STRUCT
    dwCookie DWORD    ?
    dwError DWORD ?
    pfnCallback DWORD ?
EDITSTREAM ENDS
dwCookie | application-defined value that will be passed to the callback function speficied in pfnCallback member below. We normally pass some important value to the callback function such as the file handle to use in the stream-in/out procedure.
dwError | Indicates the results of the stream-in (read) or stream-out (write) operation. A value of zero indicates no error. A nonzero value can be the return value of the EditStreamCallback function or a code indicating that the control encountered an error.
pfnCallback | Pointer to an EditStreamCallback function, which is an application-defined function that the control calls to transfer data. The control calls the callback function repeatedly, transferring a portion of the data with each call

The editstream callback function has the following definition:

    EditStreamCallback proto dwCookie:DWORD,
                 pBuffer:DWORD,
                 NumBytes:DWORD,
                 pBytesTransferred:DWORD

You have to create a function with the above prototype in your program. And then pass its address to EM_STREAMIN or EM_STREAMOUT via EDITSTREAM structure.

For stream-in operation (settting the text in the richedit control):

    dwCookie: the application-defined value you pass to EM_STREAMIN via EDITSTREAM structure. We almost always
        pass the file handle of the file we want to set its content to the control here.
    pBuffer: points to the buffer provided by the richedit control that will receive the text from your callback function.
    NumBytes: the maximum number of bytes you can write the the buffer (pBuffer) in this call. You MUST always obey this limit, ie, you can send
        less data than the value in NumBytes but must not send more data than this value. You can think of this value as the size
        of the buffer in pBuffer.
   pBytesTransferred: points to a dword that you must set the value indicating the number of bytes you actually transferred to the buffer.
        This value is usually identical to the value in NumBytes. The exception is when the data is to send is less than
        the size of the buffer provided such as when the end of file is reached.

For stream-out operation (getting the text out of the richedit control):

    dwCookie: Same as the stream-in operation. We usually pass the file handle we want to write the data to in this parameter.
    pBuffer: points to the buffer provided by the richedit control that is filled with the data from the richedit control.
        To obtain its size, you must examine the value of NumBytes.
    NumBytes: the size of the data in the buffer pointed to by pBuffer.
    pBytesTransferred: points to a dword that you must set the value indicating the number of bytes you actually read from the buffer.

The callback function returns 0 to indicate success and richedit control will continue calling the callback function if there is still data left to read/write. If some error occurs during the process and you want to stop the operation, returns a non-zero value and the richedit control will discard the data pointed to by pBuffer. The error/success value will be filled in the dwError field of EDITSTREAM so you can examine the error/success status of the stream operation after SendMessage returns.

Example:

The example below is a simple editor which you can open an asm source code file, edit and save it. It uses RichEdit control version 2.0 or above.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\gdi32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.const
IDR_MAINMENU                   equ 101
IDM_OPEN                      equ  40001
IDM_SAVE                       equ 40002
IDM_CLOSE                      equ 40003
IDM_SAVEAS                     equ 40004
IDM_EXIT                       equ 40005
IDM_COPY                      equ  40006
IDM_CUT                       equ  40007
IDM_PASTE                      equ 40008
IDM_DELETE                     equ 40009
IDM_SELECTALL                  equ 40010
IDM_OPTION 			equ 40011
IDM_UNDO			equ 40012
IDM_REDO			equ 40013
IDD_OPTIONDLG                  equ 101
IDC_BACKCOLORBOX               equ 1000
IDC_TEXTCOLORBOX               equ 1001

RichEditID 			equ 300

.data
ClassName db "IczEditClass",0
AppName  db "IczEdit version 1.0",0
RichEditDLL db "riched20.dll",0
RichEditClass db "RichEdit20A",0
NoRichEdit db "Cannot find riched20.dll",0
ASMFilterString 		db "ASM Source code (*.asm)",0,"*.asm",0
                db "All Files (*.*)",0,"*.*",0,0
OpenFileFail db "Cannot open the file",0
WannaSave db "The data in the control is modified. Want to save it?",0
FileOpened dd FALSE
BackgroundColor dd 0FFFFFFh		; default to white
TextColor dd 0		; default to black

.data?
hInstance dd ?
hRichEdit dd ?
hwndRichEdit dd ?
FileName db 256 dup(?)
AlternateFileName db 256 dup(?)
CustomColors dd 16 dup(?)

.code
start:
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke LoadLibrary,addr RichEditDLL
    .if eax!=0
        mov hRichEdit,eax
        invoke WinMain, hInstance,0,0, SW_SHOWDEFAULT
        invoke FreeLibrary,hRichEdit
    .else
        invoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR
    .endif
    invoke ExitProcess,eax

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:DWORD
    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInst
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,IDR_MAINMENU
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
           hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    .while TRUE
        invoke GetMessage, ADDR msg,0,0,0
        .break .if (!eax)
        invoke TranslateMessage, ADDR msg
        invoke DispatchMessage, ADDR msg
    .endw
    mov   eax,msg.wParam
    ret
WinMain endp

StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORD
    invoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0
    xor eax,1
    ret
StreamInProc endp

StreamOutProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesWritten:DWORD
    invoke WriteFile,hFile,pBuffer,NumBytes,pBytesWritten,0
    xor eax,1
    ret
StreamOutProc endp

CheckModifyState proc hWnd:DWORD
    invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
    .if eax!=0
        invoke MessageBox,hWnd,addr WannaSave,addr AppName,MB_YESNOCANCEL
        .if eax==IDYES
            invoke SendMessage,hWnd,WM_COMMAND,IDM_SAVE,0
        .elseif eax==IDCANCEL
            mov eax,FALSE
            ret
        .endif
    .endif
    mov eax,TRUE
    ret
CheckModifyState endp

SetColor proc
    LOCAL cfm:CHARFORMAT
    invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColor
    invoke RtlZeroMemory,addr cfm,sizeof cfm
    mov cfm.cbSize,sizeof cfm
    mov cfm.dwMask,CFM_COLOR
    push TextColor
    pop cfm.crTextColor
    invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfm
    ret
SetColor endp

OptionProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL clr:CHOOSECOLOR
    .if uMsg==WM_INITDIALOG
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDC_BACKCOLORBOX
                invoke RtlZeroMemory,addr clr,sizeof clr
                mov clr.lStructSize,sizeof clr
                push hWnd
                pop clr.hwndOwner
                push hInstance
                pop clr.hInstance
                push BackgroundColor
                pop clr.rgbResult
                mov clr.lpCustColors,offset CustomColors
                mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
                invoke ChooseColor,addr clr
                .if eax!=0
                    push clr.rgbResult
                    pop BackgroundColor
                    invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
                    invoke InvalidateRect,eax,0,TRUE
                .endif
            .elseif ax==IDC_TEXTCOLORBOX
                invoke RtlZeroMemory,addr clr,sizeof clr
                mov clr.lStructSize,sizeof clr
                push hWnd
                pop clr.hwndOwner
                push hInstance
                pop clr.hInstance
                push TextColor
                pop clr.rgbResult
                mov clr.lpCustColors,offset CustomColors
                mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
                invoke ChooseColor,addr clr
                .if eax!=0
                    push clr.rgbResult
                    pop TextColor
                    invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
                    invoke InvalidateRect,eax,0,TRUE
                .endif
            .elseif ax==IDOK
                ;==================================================================================
                ; Save the modify state of the richedit control because changing the text color changes the
                ; modify state of the richedit control.
                ;==================================================================================
                invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
                push eax
                invoke SetColor
                pop eax
                invoke SendMessage,hwndRichEdit,EM_SETMODIFY,eax,0
                invoke EndDialog,hWnd,0
            .endif
        .endif
    .elseif uMsg==WM_CTLCOLORSTATIC
        invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
        .if eax==lParam
            invoke CreateSolidBrush,BackgroundColor
            ret
        .else
            invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
            .if eax==lParam
                invoke CreateSolidBrush,TextColor
                ret
            .endif
        .endif
        mov eax,FALSE
        ret
    .elseif uMsg==WM_CLOSE
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
OptionProc endp

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL chrg:CHARRANGE
    LOCAL ofn:OPENFILENAME
    LOCAL buffer[256]:BYTE
    LOCAL editstream:EDITSTREAM
    LOCAL hFile:DWORD
    .if uMsg==WM_CREATE
        invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL,\
                CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0
        mov hwndRichEdit,eax
        ;=============================================================
        ; Set the text limit. The default is 64K
        ;=============================================================
        invoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0
        ;=============================================================
        ; Set the default text/background color
        ;=============================================================
        invoke SetColor
        invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
        invoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0
    .elseif uMsg==WM_INITMENUPOPUP
        mov eax,lParam
        .if ax==0		; file menu
            .if FileOpened==TRUE	; a file is already opened
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED
            .else
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED
            .endif
        .elseif ax==1	; edit menu
            ;=============================================================================
            ; Check whether there is some text in the clipboard. If so, we enable the paste menuitem
            ;=============================================================================
            invoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0
            .if eax==0		; no text in the clipboard
                invoke EnableMenuItem,wParam,IDM_PASTE,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_PASTE,MF_ENABLED
            .endif
            ;==========================================================
            ; check whether the undo queue is empty
            ;==========================================================
            invoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0
            .if eax==0
                invoke EnableMenuItem,wParam,IDM_UNDO,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_UNDO,MF_ENABLED
            .endif
            ;=========================================================
            ; check whether the redo queue is empty
            ;=========================================================
            invoke SendMessage,hwndRichEdit,EM_CANREDO,0,0
            .if eax==0
                invoke EnableMenuItem,wParam,IDM_REDO,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_REDO,MF_ENABLED
            .endif
            ;=========================================================
            ; check whether there is a current selection in the richedit control.
            ; If there is, we enable the cut/copy/delete menuitem
            ;=========================================================
            invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrg
            mov eax,chrg.cpMin
            .if eax==chrg.cpMax		; no current selection
                invoke EnableMenuItem,wParam,IDM_COPY,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_CUT,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_DELETE,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_COPY,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_CUT,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_DELETE,MF_ENABLED
            .endif
        .endif
    .elseif uMsg==WM_COMMAND
        .if lParam==0		; menu commands
            mov eax,wParam
            .if ax==IDM_OPEN
                invoke RtlZeroMemory,addr ofn,sizeof ofn
                mov ofn.lStructSize,sizeof ofn
                push hWnd
                pop ofn.hwndOwner
                push hInstance
                pop ofn.hInstance
                mov ofn.lpstrFilter,offset ASMFilterString
                mov ofn.lpstrFile,offset FileName
                mov byte ptr [FileName],0
                mov ofn.nMaxFile,sizeof FileName
                mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
                invoke GetOpenFileName,addr ofn
                .if eax!=0
                    invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
                    .if eax!=INVALID_HANDLE_VALUE
                        mov hFile,eax
                        ;================================================================
                        ; stream the text into the richedit control
                        ;================================================================
                        mov editstream.dwCookie,eax
                        mov editstream.pfnCallback,offset StreamInProc
                        invoke SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream
                        ;==========================================================
                        ; Initialize the modify state to false
                        ;==========================================================
                        invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
                        invoke CloseHandle,hFile
                        mov FileOpened,TRUE
                    .else
                        invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
                    .endif
                .endif
            .elseif ax==IDM_CLOSE
                invoke CheckModifyState,hWnd
                .if eax==TRUE
                    invoke SetWindowText,hwndRichEdit,0
                    mov FileOpened,FALSE
                .endif
            .elseif ax==IDM_SAVE
                invoke CreateFile,addr FileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
                .if eax!=INVALID_HANDLE_VALUE
@@:
                    mov hFile,eax
                    ;================================================================
                    ; stream the text to the file
                    ;================================================================
                    mov editstream.dwCookie,eax
                    mov editstream.pfnCallback,offset StreamOutProc
                    invoke SendMessage,hwndRichEdit,EM_STREAMOUT,SF_TEXT,addr editstream
                    ;==========================================================
                    ; Initialize the modify state to false
                    ;==========================================================
                    invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
                    invoke CloseHandle,hFile
                .else
                    invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
                .endif
            .elseif ax==IDM_COPY
                invoke SendMessage,hwndRichEdit,WM_COPY,0,0
            .elseif ax==IDM_CUT
                invoke SendMessage,hwndRichEdit,WM_CUT,0,0
            .elseif ax==IDM_PASTE
                invoke SendMessage,hwndRichEdit,WM_PASTE,0,0
            .elseif ax==IDM_DELETE
                invoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0
            .elseif ax==IDM_SELECTALL
                mov chrg.cpMin,0
                mov chrg.cpMax,-1
                invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg
            .elseif ax==IDM_UNDO
                invoke SendMessage,hwndRichEdit,EM_UNDO,0,0
            .elseif ax==IDM_REDO
                invoke SendMessage,hwndRichEdit,EM_REDO,0,0
            .elseif ax==IDM_OPTION
                invoke DialogBoxParam,hInstance,IDD_OPTIONDLG,hWnd,addr OptionProc,0
            .elseif ax==IDM_SAVEAS
                invoke RtlZeroMemory,addr ofn,sizeof ofn
                mov ofn.lStructSize,sizeof ofn
                push hWnd
                pop ofn.hwndOwner
                push hInstance
                pop ofn.hInstance
                mov ofn.lpstrFilter,offset ASMFilterString
                mov ofn.lpstrFile,offset AlternateFileName
                mov byte ptr [AlternateFileName],0
                mov ofn.nMaxFile,sizeof AlternateFileName
                mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
                invoke GetSaveFileName,addr ofn
                .if eax!=0
                    invoke CreateFile,addr AlternateFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
                    .if eax!=INVALID_HANDLE_VALUE
                        jmp @B
                    .endif
                .endif
            .elseif ax==IDM_EXIT
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        invoke CheckModifyState,hWnd
        .if eax==TRUE
            invoke DestroyWindow,hWnd
        .endif
    .elseif uMsg==WM_SIZE
        mov eax,lParam
        mov edx,eax
        and eax,0FFFFh
        shr edx,16
        invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE
    .elseif uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .else
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
WndProc endp
end start

;===================================================================
; The resource file
;===================================================================
#include "resource.h"
#define IDR_MAINMENU                    101
#define IDD_OPTIONDLG                   101
#define IDC_BACKCOLORBOX                1000
#define IDC_TEXTCOLORBOX                1001
#define IDM_OPEN                        40001
#define IDM_SAVE                        40002
#define IDM_CLOSE                       40003
#define IDM_SAVEAS                      40004
#define IDM_EXIT                        40005
#define IDM_COPY                        40006
#define IDM_CUT                         40007
#define IDM_PASTE                       40008
#define IDM_DELETE                      40009
#define IDM_SELECTALL                   40010
#define IDM_OPTION                      40011
#define IDM_UNDO                        40012
#define IDM_REDO                        40013

IDR_MAINMENU MENU DISCARDABLE
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&Open",                       IDM_OPEN
        MENUITEM "&Close",                      IDM_CLOSE
        MENUITEM "&Save",                       IDM_SAVE
        MENUITEM "Save &As",                    IDM_SAVEAS
        MENUITEM SEPARATOR
        MENUITEM "E&xit",                       IDM_EXIT
    END
    POPUP "&Edit"
    BEGIN
        MENUITEM "&Undo",                       IDM_UNDO
        MENUITEM "&Redo",                       IDM_REDO
        MENUITEM "&Copy",                       IDM_COPY
        MENUITEM "C&ut",                        IDM_CUT
        MENUITEM "&Paste",                      IDM_PASTE
        MENUITEM SEPARATOR
        MENUITEM "&Delete",                     IDM_DELETE
        MENUITEM SEPARATOR
        MENUITEM "Select &All",                 IDM_SELECTALL
    END
    MENUITEM "Options",                     IDM_OPTION
END

IDD_OPTIONDLG DIALOG DISCARDABLE  0, 0, 183, 54
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_CENTER
CAPTION "Options"
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,137,7,39,14
    PUSHBUTTON      "Cancel",IDCANCEL,137,25,39,14
    GROUPBOX        "",IDC_STATIC,5,0,124,49
    LTEXT           "Background Color:",IDC_STATIC,20,14,60,8
    LTEXT           "",IDC_BACKCOLORBOX,85,11,28,14,SS_NOTIFY | WS_BORDER
    LTEXT           "Text Color:",IDC_STATIC,20,33,35,8
    LTEXT           "",IDC_TEXTCOLORBOX,85,29,28,14,SS_NOTIFY | WS_BORDER
END

Analysis:

The program first loads the richedit dll, which in this case is riched20.dll. If the dll cannot be loaded, it exits to Windows.

invoke LoadLibrary,addr RichEditDLL
.if eax!=0
    mov hRichEdit,eax
    invoke WinMain,hInstance,0,0, SW_SHOWDEFAULT
    invoke FreeLibrary,hRichEdit
.else
    invoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR
.endif
invoke ExitProcess,eax

After the dll is loaded successfully, we proceed to create a normal window which will be the parent of the richedit control. Within the WM_CREATE handler, we create the richedit control:

        invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL,\
                CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0
        mov hwndRichEdit,eax

Note that we specify ES_MULTILINE style else the control will be a single-lined one.

        invoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0

After the richedit control is created, we must set the new text limit on it. By default, the richedit control has 64K text limit, the same as a simple multi-line edit control. We must extend this limit to allow it to operate with larger files. In the above line, I specify -1 which amounts to 0FFFFFFFFh, a very large value.

       invoke SetColor

Next, we set the text/background color. Since this operation can be performed in other part of the program, I put the code in a function named SetColor.

SetColor proc
    LOCAL cfm:CHARFORMAT
    invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColor

Setting the background color of the richedit control is a straightforward operation: just send EM_SETBKGNDCOLOR message to the richedit control. (If you use a multi-line edit control, you have to process WM_CTLCOLOREDIT). The default background color is white.

    invoke RtlZeroMemory,addr cfm,sizeof cfm
    mov cfm.cbSize,sizeof cfm
    mov cfm.dwMask,CFM_COLOR
    push TextColor
    pop cfm.crTextColor

After the background color is set, we fill in the members of CHARFORMAT in order to set the text color. Note that we fill cbSize with the size of the structure so the richedit control knows we are sending it CHARFORMAT, not CHARFORMAT2. dwMask has only one flag, CFM_COLOR, because we only want to set the text color and crTextColor is filled with the value of the desired text color.

    invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfm
    ret
SetColor endp

After settting the color, you have to empty undo buffer simply because the act of changing text/background color is undo-able. We send EM_EMPTYUNDOBUFFER message to achieve this.

    invoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0

After filling the CHARFORMAT structure, we send EM_SETCHARFORMAT to the richedit control, specifying SCF_ALL flag in wParam to indicate that we want the text formatting to be applied to all text in the control.

Note that when we first created the richedit control, we didn't specify its size/position at that time. That's because we want it to cover the whole client area of the parent window. We resize it whenever the size of the parent window changes.

    .elseif uMsg==WM_SIZE
        mov eax,lParam
        mov edx,eax
        and eax,0FFFFh
        shr edx,16
        invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE

In the above code snippet, we use the new dimension of the client area passed in lParam to resize the richedit control with MoveWindow.

When the user clicks on the File/Edit menu bar, we process WM_INITPOPUPMENU so that we can prepare the states of the menuitems in the submenu before displaying it to the user. For example, if a file is already opened in the richedit control, we want to disable the open menuitem and enable all the remaining menuitems.

In the case of the File menu bar, we use the variable FileOpened as the flag to determine whether a file is already opened. If the value in this variable is TRUE, we know that a file is already opened.

    .elseif uMsg==WM_INITMENUPOPUP
        mov eax,lParam
        .if ax==0		; file menu
            .if FileOpened==TRUE	; a file is already opened
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED
            .else
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED
            .endif

As you can see, if a file is already opened, we gray out the open menuitem and enable the remaining menuitems. The reverse is true of FileOpened is false.

In the case of the edit menu bar, we need to check the state of the richedit control/clipboard first.

            invoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0
            .if eax==0		; no text in the clipboard
                invoke EnableMenuItem,wParam,IDM_PASTE,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_PASTE,MF_ENABLED
            .endif

We first check whether some text is available in the clipboard by sending EM_CANPASTE message. If some text is available, SendMessage returns TRUE and we enable the paste menuitem. If not, we gray out the menuitem.

            invoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0
            .if eax==0
                invoke EnableMenuItem,wParam,IDM_UNDO,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_UNDO,MF_ENABLED
            .endif

Next, we check whether the undo buffer is empty by sending EM_CANUNDO message. If it's not empty, SendMessage returns TRUE and we enable the undo menuitem.

            invoke SendMessage,hwndRichEdit,EM_CANREDO,0,0
            .if eax==0
                invoke EnableMenuItem,wParam,IDM_REDO,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_REDO,MF_ENABLED
            .endif

We check the redo buffer by sending EM_CANREDO message to the richedit control. If it's not empty, SendMessage returns TRUE and we enable the redo menuitem.

            invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrg
            mov eax,chrg.cpMin
            .if eax==chrg.cpMax		; no current selection
                invoke EnableMenuItem,wParam,IDM_COPY,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_CUT,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_DELETE,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_COPY,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_CUT,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_DELETE,MF_ENABLED
            .endif

Lastly, we check whether a current selection exists by sending EM_EXGETSEL message. This message uses a CHARRANGE structure which is defined as follows:

CHARRANGE STRUCT
  cpMin  DWORD      ?
  cpMax  DWORD      ?
CHARRANGE ENDS

cpMin contains the character position index immediately preceding the first character in the range.
cpMax contains the character position immediately following the last character in the range.

After EM_EXGETSEL returns, the CHARRANGE structure is filled with the starting-ending character position indices of the selection range. If there is no current selection, cpMin and cpMax are identical and we gray out the cut/copy/delete menuitems.

When the user clicks the Open menuitem, we display an open file dialog box and if the user selects a file, we open the file and stream its content to the richedit control.

                    invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
                    .if eax!=INVALID_HANDLE_VALUE
                        mov hFile,eax

                        mov editstream.dwCookie,eax
                        mov editstream.pfnCallback,offset StreamInProc
                        invoke SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream

After the file is successfully opened with CreateFile, we fill the EDITSTREAM structure in preparation for EM_STREAMIN message. We choose to send the handle to the opened file via dwCookie member and pass the address of the stream callback function in pfnCallback.

The stream callback procedure itself is the essence of simplicity.

StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORD
    invoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0
    xor eax,1
    ret
StreamInProc endp

You can see that all parameters of the stream callback procedure fit perfectly with ReadFile. And the return value of ReadFile is xor-ed with 1 so that if it returns 1 (success), the actual value returned in eax is 0 and vice versa.

    invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
    invoke CloseHandle,hFile
    mov FileOpened,TRUE

After EM_STREAMIN returns, it means the stream operation is completed. In reality, we must check the value of dwError member of the EDITSTREAM structure.

Richedit (and edit) control supports a flag to indicate whether its content is modified. We can obtain the value of this flag by sending EM_GETMODIFY message to the control. SendMessage returns TRUE if the content of the control was modified. Since we stream the text into the control, it's a kind of a modification. We must set the modify flag to FALSE by sending EM_SETMODIFY with wParam==FALSE to the control to start anew after the stream-in opertion is finished. We immediately close the file and set FileOpened to TRUE to indicate that a file was opened.

When the user clicks on save/saveas menuitem, we use EM_STREAMOUT message to output the content of the richedit control to a file. As with the streamin callback function, the stream-out callback function is simplicity in itself. It fits perfectly with WriteFile.

The text operations such as cut/copy/paste/redo/undo are easily implemented by sending single message to the richedit control, WM_CUT/WM_COPY/WM_PASTE/WM_REDO/WM_UNDO respectively.

The delete/select all operations are done as follows:

            .elseif ax==IDM_DELETE
                invoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0
            .elseif ax==IDM_SELECTALL
                mov chrg.cpMin,0
                mov chrg.cpMax,-1
                invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg

The delete operation affects the currently selection. I send EM_REPLACESEL message with NULL string so the richedit control will replace the currently selected text with the null string.

The select-all operation is done by sending EM_EXSETSEL message, specifying cpMin==0 and cpMax==-1 which amounts to selecting all the text.

When the user selects Option menu bar, we display a dialog box presenting the current background/text colors.

When the user clicks on one of the color boxes, it displays the choose-color dialog box. The "color box" is in fact a static control with SS_NOTIFY and WS_BORDER flag. A static control with SS_NOTIFY flag will notify its parent window with mouse actions on it, such as BN_CLICKED (STN_CLICKED). That's the trick.

            .elseif ax==IDC_BACKCOLORBOX
                invoke RtlZeroMemory,addr clr,sizeof clr
                mov clr.lStructSize,sizeof clr
                push hWnd
                pop clr.hwndOwner
                push hInstance
                pop clr.hInstance
                push BackgroundColor
                pop clr.rgbResult
                mov clr.lpCustColors,offset CustomColors
                mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
                invoke ChooseColor,addr clr
                .if eax!=0
                    push clr.rgbResult
                    pop BackgroundColor
                    invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
                    invoke InvalidateRect,eax,0,TRUE
                .endif

When the user clicks on one of the color box, we fill the members of the CHOOSECOLOR structure and call ChooseColor to display the choose-color dialog box. If the user selects a color, the colorref value is returned in rgbResult member and we store that value in BackgroundColor variable. After that, we force a repaint on the color box by calling InvalidateRect on the handle to the color box. The color box sends WM_CTLCOLORSTATIC message to its parent window.

        invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
        .if eax==lParam
            invoke CreateSolidBrush,BackgroundColor
            ret

Within the WM_CTLCOLORSTATIC handler, we compare the handle of the static control passed in lParam to that of both the color boxes. If the values match, we create a new brush using the color in the variable and immediately return. The static control will use the newly created brush to paint its background.

[Iczelion's Win32 Assembly Homepage]


Iczelion's Win32 Assembly Tutorial No.34: RichEdit Control: More Text Operations

Tutorial 34: RichEdit Control: More Text Operations

You'll learn more about text operations under RichEdit control. Specifically, you'll know how to search for/replace text, jumping to specific line number.

Download the example.

Theory

Searching for Text

There are several text operations under RichEdit control. Searching for text is one such operation. Searching for text is done by sending EM_FINDTEXT or EM_FINDTEXTEX. These messages has a small difference.

EM_FINDTEXT
wParam == Search options. Can be any combination of the values in the table below.These options are identical for both
            EM_FINDTEXT and EM_FINDTEXTEX
FR_DOWN If this flag is specified, the search starts from the end of the current selection to the end of the text in the control (downward). This flag has effect only for RichEdit 2.0 or later: This behavior is the default for RichEdit 1.0. The default behavior of RichEdit 2.0 or later is to search from the end of the current selection to the beginning of the text (upward).
In summary, if you use RichEdit 1.0, you can't do anything about the search direction: it always searches downward. If you use RichEdit 2.0 and you want to search downward, you must specify this flag else the search would be upward.
FR_MATCHCASE | If this flag is specified, the search is case-sensitive.
FR_WHOLEWORD | If this flag is set, the search finds the whole word that matches the specified search string.
Actually, there are a few more flags but they are relevant to non-English languages.
lParam == pointer to the FINDTEXT structure.

            FINDTEXT STRUCT
              chrg          CHARRANGE  <>
              lpstrText     DWORD      ?
            FINDTEXT ENDS

chrg is a CHARRANGE structure which is defined as follows:

            CHARRANGE STRUCT
              cpMin  DWORD      ?
              cpMax  DWORD      ?
            CHARRANGE ENDS

cpMin contains the character index of the first character in the character array (range).
cpMax contains the character index of the character immediately following the last character in the character array.

In essence, to search for a text string, you have to specify the character range in which to search. The meaning of cpMin
and cpMax differ according to whether the search is downward or upward. If the search is downward, cpMin
specifies the starting character index to search in and cpMax the ending character index. If the search is upward, the
reverse is true, ie. cpMin contains the ending character index while cpMax the starting character index.

lpstrText is the pointer to the text string to search for.

EM_FINDTEXT returns the character index of the first character in the matching text string in the richedit control. It returns -1 if
no match is found.

EM_FINDTEXTEX
wParam == the search options. Same as those of EM_FINDTEXT.
lParam == pointer to the FINDTEXTEX structure.

            FINDTEXTEX STRUCT
              chrg          CHARRANGE  <>
              lpstrText     DWORD      ?
              chrgText	CHARRANGE <>
            FINDTEXTEX ENDS

The first two members of FINDTEXTEX are identical to those of FINDTEXT structure. chrgText is a CHARRANGE structure that will
be filled with the starting/ending characterindices if a match is found.

The return value of EM_FINDTEXTEX is the same as that of EM_FINDTEXT.

The difference between EM_FINDTEXT and EM_FINDTEXTEX is that the FINDTEXTEX structure has an additional member,
chrgText, which will be filled with the starting/ending character indices if a match is found. This is convenient if we want to do
more text operations on the string.

Replace/Insert Text

RichEdit control provides EM_SETTEXTEX for replacing/inserting text. This message combines the functionality of WM_SETTEXT and EM_REPLACESEL. It has the following syntax:

    EM_SETTEXTEX
    wParam == pointer to SETTEXTEX structure.

            SETTEXTEX STRUCT
              flags          DWORD      ?
              codepage       DWORD      ?
            SETTEXTEX ENDS

    flags can be the combination of the following values:
ST_DEFAULT | Deletes the undo stack, discards rich-text formatting, replaces all text.
ST_KEEPUNDO | Keeps the undo stack
ST_SELECTION | Replaces selection and keeps rich-text formatting
    codepage is the constant that specifies the code page you want to text to be. Usually, we simply use CP_ACP.

Text Selection

We can select the text programmatically with EM_SETSEL or EM_EXSETSEL. Either one works fine. Choosing which message to use depends on the available format of the character indices. If they are already stored in a CHARRANGE structure, it's easier to use EM_EXSETSEL.

    EM_EXSETSEL
    wParam == not used. Must be 0
    lParam == pointer to a CHARRANGE structure that contains the character range to be selected.

Event Notification

In the case of a multiline edit control, you have to subclass it in order to obtain the input messages such as mouse/keyboard events. RichEdit control provides a better scheme that will notify the parent window of such events. In order to register for notifications, the parent window sends EM_SETEVENTMASK message to the RichEdit control, specifying which events it's interested in. EM_SETEVENTMASK has the following syntax:

    EM_SETEVENTMASK
    wParam == not used. Must be 0
    lParam == event mask value. It can be the combination of the flags in the table below.
ENM_CHANGE | Sends EN_CHANGE notifications
ENM_CORRECTTEXT | Sends EN_CORRECTTEXT notifications
ENM_DRAGDROPDONE | Sends EN_DRAGDROPDONE notifications
ENM_DROPFILES | Sends EN_DROPFILES notifications.
ENM_KEYEVENTS | Sends EN_MSGFILTER notifications for keyboard events
ENM_LINK | Rich Edit 2.0 and later: Sends EN_LINK notifications when the mouse pointer is over text that has the CFE_LINK and one of several mouse actions is performed.
ENM_MOUSEEVENTS | Sends EN_MSGFILTER notifications for mouse events
ENM_OBJECTPOSITIONS | Sends EN_OBJECTPOSITIONS notifications
ENM_PROTECTED | Sends EN_PROTECTED notifications
ENM_REQUESTRESIZE | Sends EN_REQUESTRESIZE notifications
ENM_SCROLL | Sends EN_HSCROLL and EN_VSCROLL notifications
ENM_SCROLLEVENTS | Sends EN_MSGFILTER notifications for mouse wheel events
ENM_SELCHANGE | Sends EN_SELCHANGE notifications
ENM_UPDATE | Sends EN_UPDATE notifications. Rich Edit 2.0 and later: this flag is ignored and the EN_UPDATE notifications are always sent. However, if Rich Edit 3.0 emulates Rich Edit 1.0, you must use this flag to send EN_UPDATE notifications

All the above notifications will be sent as WM_NOTIFY message: you have to check the code member of NMHDR structure for the notification message. For example, if you want to register for mouse events (eg. you want to provide a context sensitive popup menu), you must do something like this:

    invoke SendMessage,hwndRichEdit,EM_SETEVENTMASK,0,ENM_MOUSEEVENTS
    .....
    .....
    WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .....
    ....
        .elseif uMsg==WM_NOTIFY
            push esi
            mov esi,lParam
            assume esi:ptr NMHDR
            .if [esi].code==EN_MSGFILTER
                ....
                [ do something here]
                ....
            .endif
            pop esi

Example:

The following example is the update of IczEdit in tutorial no. 33. It adds search/replace functionality and accelerator keys to the program. It also processes the mouse events and provides a popup menu on right mouse click.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\gdi32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.const
IDR_MAINMENU                   equ 101
IDM_OPEN                      equ  40001
IDM_SAVE                       equ 40002
IDM_CLOSE                      equ 40003
IDM_SAVEAS                     equ 40004
IDM_EXIT                       equ 40005
IDM_COPY                      equ  40006
IDM_CUT                       equ  40007
IDM_PASTE                      equ 40008
IDM_DELETE                     equ 40009
IDM_SELECTALL                  equ 40010
IDM_OPTION 			equ 40011
IDM_UNDO			equ 40012
IDM_REDO			equ 40013
IDD_OPTIONDLG                  equ 101
IDC_BACKCOLORBOX               equ 1000
IDC_TEXTCOLORBOX               equ 1001
IDR_MAINACCEL                 equ  105
IDD_FINDDLG                    equ 102
IDD_GOTODLG                    equ 103
IDD_REPLACEDLG                 equ 104
IDC_FINDEDIT                  equ  1000
IDC_MATCHCASE                  equ 1001
IDC_REPLACEEDIT                 equ 1001
IDC_WHOLEWORD                  equ 1002
IDC_DOWN                       equ 1003
IDC_UP                       equ   1004
IDC_LINENO                   equ   1005
IDM_FIND                       equ 40014
IDM_FINDNEXT                  equ  40015
IDM_REPLACE                     equ 40016
IDM_GOTOLINE                   equ 40017
IDM_FINDPREV                  equ  40018
RichEditID 			equ 300

.data
ClassName db "IczEditClass",0
AppName  db "IczEdit version 2.0",0
RichEditDLL db "riched20.dll",0
RichEditClass db "RichEdit20A",0
NoRichEdit db "Cannot find riched20.dll",0
ASMFilterString 		db "ASM Source code (*.asm)",0,"*.asm",0
                db "All Files (*.*)",0,"*.*",0,0
OpenFileFail db "Cannot open the file",0
WannaSave db "The data in the control is modified. Want to save it?",0
FileOpened dd FALSE
BackgroundColor dd 0FFFFFFh		; default to white
TextColor dd 0		; default to black
hSearch dd ?		; handle to the search/replace dialog box
hAccel dd ?

.data?
hInstance dd ?
hRichEdit dd ?
hwndRichEdit dd ?
FileName db 256 dup(?)
AlternateFileName db 256 dup(?)
CustomColors dd 16 dup(?)
FindBuffer db 256 dup(?)
ReplaceBuffer db 256 dup(?)
uFlags dd ?
findtext FINDTEXTEX <>

.code
start:
    mov byte ptr [FindBuffer],0
    mov byte ptr [ReplaceBuffer],0
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke LoadLibrary,addr RichEditDLL
    .if eax!=0
        mov hRichEdit,eax
        invoke WinMain, hInstance,0,0, SW_SHOWDEFAULT
        invoke FreeLibrary,hRichEdit
    .else
        invoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR
    .endif
    invoke ExitProcess,eax

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:DWORD
    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInst
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,IDR_MAINMENU
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
           hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    invoke LoadAccelerators,hInstance,IDR_MAINACCEL
    mov hAccel,eax
    .while TRUE
        invoke GetMessage, ADDR msg,0,0,0
        .break .if (!eax)
        invoke IsDialogMessage,hSearch,addr msg
        .if eax==FALSE
            invoke TranslateAccelerator,hwnd,hAccel,addr msg
            .if eax==0
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .endif
        .endif
    .endw
    mov   eax,msg.wParam
    ret
WinMain endp

StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORD
    invoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0
    xor eax,1
    ret
StreamInProc endp

StreamOutProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesWritten:DWORD
    invoke WriteFile,hFile,pBuffer,NumBytes,pBytesWritten,0
    xor eax,1
    ret
StreamOutProc endp

CheckModifyState proc hWnd:DWORD
    invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
    .if eax!=0
        invoke MessageBox,hWnd,addr WannaSave,addr AppName,MB_YESNOCANCEL
        .if eax==IDYES
            invoke SendMessage,hWnd,WM_COMMAND,IDM_SAVE,0
        .elseif eax==IDCANCEL
            mov eax,FALSE
            ret
        .endif
    .endif
    mov eax,TRUE
    ret
CheckModifyState endp

SetColor proc
    LOCAL cfm:CHARFORMAT
    invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColor
    invoke RtlZeroMemory,addr cfm,sizeof cfm
    mov cfm.cbSize,sizeof cfm
    mov cfm.dwMask,CFM_COLOR
    push TextColor
    pop cfm.crTextColor
    invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfm
    ret
SetColor endp

OptionProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL clr:CHOOSECOLOR
    .if uMsg==WM_INITDIALOG
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDC_BACKCOLORBOX
                invoke RtlZeroMemory,addr clr,sizeof clr
                mov clr.lStructSize,sizeof clr
                push hWnd
                pop clr.hwndOwner
                push hInstance
                pop clr.hInstance
                push BackgroundColor
                pop clr.rgbResult
                mov clr.lpCustColors,offset CustomColors
                mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
                invoke ChooseColor,addr clr
                .if eax!=0
                    push clr.rgbResult
                    pop BackgroundColor
                    invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
                    invoke InvalidateRect,eax,0,TRUE
                .endif
            .elseif ax==IDC_TEXTCOLORBOX
                invoke RtlZeroMemory,addr clr,sizeof clr
                mov clr.lStructSize,sizeof clr
                push hWnd
                pop clr.hwndOwner
                push hInstance
                pop clr.hInstance
                push TextColor
                pop clr.rgbResult
                mov clr.lpCustColors,offset CustomColors
                mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
                invoke ChooseColor,addr clr
                .if eax!=0
                    push clr.rgbResult
                    pop TextColor
                    invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
                    invoke InvalidateRect,eax,0,TRUE
                .endif
            .elseif ax==IDOK
                invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
                push eax
                invoke SetColor
                pop eax
                invoke SendMessage,hwndRichEdit,EM_SETMODIFY,eax,0
                invoke EndDialog,hWnd,0
            .endif
        .endif
    .elseif uMsg==WM_CTLCOLORSTATIC
        invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
        .if eax==lParam
            invoke CreateSolidBrush,BackgroundColor
            ret
        .else
            invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
            .if eax==lParam
                invoke CreateSolidBrush,TextColor
                ret
            .endif
        .endif
        mov eax,FALSE
        ret
    .elseif uMsg==WM_CLOSE
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
OptionProc endp

SearchProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .if uMsg==WM_INITDIALOG
        push hWnd
        pop hSearch
        invoke CheckRadioButton,hWnd,IDC_DOWN,IDC_UP,IDC_DOWN
        invoke SendDlgItemMessage,hWnd,IDC_FINDEDIT,WM_SETTEXT,0,addr FindBuffer
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDOK
                mov uFlags,0
                invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
                invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
                .if eax!=0
                    invoke IsDlgButtonChecked,hWnd,IDC_DOWN
                    .if eax==BST_CHECKED
                        or uFlags,FR_DOWN
                        mov eax,findtext.chrg.cpMin
                        .if eax!=findtext.chrg.cpMax
                            push findtext.chrg.cpMax
                            pop findtext.chrg.cpMin
                        .endif
                        mov findtext.chrg.cpMax,-1
                    .else
                        mov findtext.chrg.cpMax,0
                    .endif
                    invoke IsDlgButtonChecked,hWnd,IDC_MATCHCASE
                    .if eax==BST_CHECKED
                        or uFlags,FR_MATCHCASE
                    .endif
                    invoke IsDlgButtonChecked,hWnd,IDC_WHOLEWORD
                    .if eax==BST_CHECKED
                        or uFlags,FR_WHOLEWORD
                    .endif
                    mov findtext.lpstrText,offset FindBuffer
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,uFlags,addr findtext
                    .if eax!=-1
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                    .endif
                .endif
            .elseif ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .else
                mov eax,FALSE
                ret
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        mov hSearch,0
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
SearchProc endp

ReplaceProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL settext:SETTEXTEX
    .if uMsg==WM_INITDIALOG
        push hWnd
        pop hSearch
        invoke SetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer
        invoke SetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDOK
                invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
                invoke GetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer,sizeof ReplaceBuffer
                mov findtext.chrg.cpMin,0
                mov findtext.chrg.cpMax,-1
                mov findtext.lpstrText,offset FindBuffer
                mov settext.flags,ST_SELECTION
                mov settext.codepage,CP_ACP
                .while TRUE
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext
                    .if eax==-1
                        .break
                    .else
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                        invoke SendMessage,hwndRichEdit,EM_SETTEXTEX,addr settext,addr ReplaceBuffer
                    .endif
                .endw
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        mov hSearch,0
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
ReplaceProc endp

GoToProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL LineNo:DWORD
    LOCAL chrg:CHARRANGE
    .if uMsg==WM_INITDIALOG
        push hWnd
        pop hSearch
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDOK
                invoke GetDlgItemInt,hWnd,IDC_LINENO,NULL,FALSE
                mov LineNo,eax
                invoke SendMessage,hwndRichEdit,EM_GETLINECOUNT,0,0
                .if eax>LineNo
                    invoke SendMessage,hwndRichEdit,EM_LINEINDEX,LineNo,0
                    mov chrg.cpMin,eax
                    mov chrg.cpMax,eax
                    invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg
                    invoke SetFocus,hwndRichEdit
                .endif
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        mov hSearch,0
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
GoToProc endp

PrepareEditMenu proc hSubMenu:DWORD
    LOCAL chrg:CHARRANGE
    invoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0
    .if eax==0		; no text in the clipboard
        invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_ENABLED
    .endif
    invoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0
    .if eax==0
        invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_ENABLED
    .endif
    invoke SendMessage,hwndRichEdit,EM_CANREDO,0,0
    .if eax==0
        invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_ENABLED
    .endif
    invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrg
    mov eax,chrg.cpMin
    .if eax==chrg.cpMax		; no current selection
        invoke EnableMenuItem,hSubMenu,IDM_COPY,MF_GRAYED
        invoke EnableMenuItem,hSubMenu,IDM_CUT,MF_GRAYED
        invoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_COPY,MF_ENABLED
        invoke EnableMenuItem,hSubMenu,IDM_CUT,MF_ENABLED
        invoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_ENABLED
    .endif
    ret
PrepareEditMenu endp

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL ofn:OPENFILENAME
    LOCAL buffer[256]:BYTE
    LOCAL editstream:EDITSTREAM
    LOCAL hFile:DWORD
    LOCAL hPopup:DWORD
    LOCAL pt:POINT
    LOCAL chrg:CHARRANGE
    .if uMsg==WM_CREATE
        invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL,\
                CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0
        mov hwndRichEdit,eax
        invoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0
        invoke SetColor
        invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
        invoke SendMessage,hwndRichEdit,EM_SETEVENTMASK,0,ENM_MOUSEEVENTS
        invoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0
    .elseif uMsg==WM_NOTIFY
        push esi
        mov esi,lParam
        assume esi:ptr NMHDR
        .if [esi].code==EN_MSGFILTER
            assume esi:ptr MSGFILTER
            .if [esi].msg==WM_RBUTTONDOWN
                invoke GetMenu,hWnd
                invoke GetSubMenu,eax,1
                mov hPopup,eax
                invoke PrepareEditMenu,hPopup
                mov edx,[esi].lParam
                mov ecx,edx
                and edx,0FFFFh
                shr ecx,16
                mov pt.x,edx
                mov pt.y,ecx
                invoke ClientToScreen,hWnd,addr pt
                invoke TrackPopupMenu,hPopup,TPM_LEFTALIGN or TPM_BOTTOMALIGN,pt.x,pt.y,NULL,hWnd,NULL
            .endif
        .endif
        pop esi
    .elseif uMsg==WM_INITMENUPOPUP
        mov eax,lParam
        .if ax==0		; file menu
            .if FileOpened==TRUE	; a file is already opened
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED
            .else
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED
            .endif
        .elseif ax==1	; edit menu
            invoke PrepareEditMenu,wParam
        .elseif ax==2		; search menu bar
            .if FileOpened==TRUE
                invoke EnableMenuItem,wParam,IDM_FIND,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_FINDPREV,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_REPLACE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_ENABLED
            .else
                invoke EnableMenuItem,wParam,IDM_FIND,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_FINDPREV,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_REPLACE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_GRAYED
            .endif
        .endif
    .elseif uMsg==WM_COMMAND
        .if lParam==0		; menu commands
            mov eax,wParam
            .if ax==IDM_OPEN
                invoke RtlZeroMemory,addr ofn,sizeof ofn
                mov ofn.lStructSize,sizeof ofn
                push hWnd
                pop ofn.hwndOwner
                push hInstance
                pop ofn.hInstance
                mov ofn.lpstrFilter,offset ASMFilterString
                mov ofn.lpstrFile,offset FileName
                mov byte ptr [FileName],0
                mov ofn.nMaxFile,sizeof FileName
                mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
                invoke GetOpenFileName,addr ofn
                .if eax!=0
                    invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
                    .if eax!=INVALID_HANDLE_VALUE
                        mov hFile,eax
                        ;================================================================
                        ; stream the text into the richedit control
                        ;================================================================
                        mov editstream.dwCookie,eax
                        mov editstream.pfnCallback,offset StreamInProc
                        invoke SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream
                        ;==========================================================
                        ; Initialize the modify state to false
                        ;==========================================================
                        invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
                        invoke CloseHandle,hFile
                        mov FileOpened,TRUE
                    .else
                        invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
                    .endif
                .endif
            .elseif ax==IDM_CLOSE
                invoke CheckModifyState,hWnd
                .if eax==TRUE
                    invoke SetWindowText,hwndRichEdit,0
                    mov FileOpened,FALSE
                .endif
            .elseif ax==IDM_SAVE
                invoke CreateFile,addr FileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
                .if eax!=INVALID_HANDLE_VALUE
@@:
                    mov hFile,eax
                    ;================================================================
                    ; stream the text to the file
                    ;================================================================
                    mov editstream.dwCookie,eax
                    mov editstream.pfnCallback,offset StreamOutProc
                    invoke SendMessage,hwndRichEdit,EM_STREAMOUT,SF_TEXT,addr editstream
                    ;==========================================================
                    ; Initialize the modify state to false
                    ;==========================================================
                    invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
                    invoke CloseHandle,hFile
                .else
                    invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
                .endif
            .elseif ax==IDM_COPY
                invoke SendMessage,hwndRichEdit,WM_COPY,0,0
            .elseif ax==IDM_CUT
                invoke SendMessage,hwndRichEdit,WM_CUT,0,0
            .elseif ax==IDM_PASTE
                invoke SendMessage,hwndRichEdit,WM_PASTE,0,0
            .elseif ax==IDM_DELETE
                invoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0
            .elseif ax==IDM_SELECTALL
                mov chrg.cpMin,0
                mov chrg.cpMax,-1
                invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg
            .elseif ax==IDM_UNDO
                invoke SendMessage,hwndRichEdit,EM_UNDO,0,0
            .elseif ax==IDM_REDO
                invoke SendMessage,hwndRichEdit,EM_REDO,0,0
            .elseif ax==IDM_OPTION
                invoke DialogBoxParam,hInstance,IDD_OPTIONDLG,hWnd,addr OptionProc,0
            .elseif ax==IDM_SAVEAS
                invoke RtlZeroMemory,addr ofn,sizeof ofn
                mov ofn.lStructSize,sizeof ofn
                push hWnd
                pop ofn.hwndOwner
                push hInstance
                pop ofn.hInstance
                mov ofn.lpstrFilter,offset ASMFilterString
                mov ofn.lpstrFile,offset AlternateFileName
                mov byte ptr [AlternateFileName],0
                mov ofn.nMaxFile,sizeof AlternateFileName
                mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
                invoke GetSaveFileName,addr ofn
                .if eax!=0
                    invoke CreateFile,addr AlternateFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
                    .if eax!=INVALID_HANDLE_VALUE
                        jmp @B
                    .endif
                .endif
            .elseif ax==IDM_FIND
                .if hSearch==0
                    invoke CreateDialogParam,hInstance,IDD_FINDDLG,hWnd,addr SearchProc,0
                .endif
            .elseif ax==IDM_REPLACE
                .if hSearch==0
                    invoke CreateDialogParam,hInstance,IDD_REPLACEDLG,hWnd,addr ReplaceProc,0
                .endif
            .elseif ax==IDM_GOTOLINE
                .if hSearch==0
                    invoke CreateDialogParam,hInstance,IDD_GOTODLG,hWnd,addr GoToProc,0
                .endif
            .elseif ax==IDM_FINDNEXT
                invoke lstrlen,addr FindBuffer
                .if eax!=0
                    invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
                    mov eax,findtext.chrg.cpMin
                    .if eax!=findtext.chrg.cpMax
                        push findtext.chrg.cpMax
                        pop findtext.chrg.cpMin
                    .endif
                    mov findtext.chrg.cpMax,-1
                    mov findtext.lpstrText,offset FindBuffer
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext
                    .if eax!=-1
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                    .endif
                .endif
            .elseif ax==IDM_FINDPREV
                invoke lstrlen,addr FindBuffer
                .if eax!=0
                    invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
                    mov findtext.chrg.cpMax,0
                    mov findtext.lpstrText,offset FindBuffer
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,0,addr findtext
                    .if eax!=-1
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                    .endif
                .endif
            .elseif ax==IDM_EXIT
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        invoke CheckModifyState,hWnd
        .if eax==TRUE
            invoke DestroyWindow,hWnd
        .endif
    .elseif uMsg==WM_SIZE
        mov eax,lParam
        mov edx,eax
        and eax,0FFFFh
        shr edx,16
        invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE
    .elseif uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .else
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
WndProc endp
end start

Analysis

The search-for-text capability is implemented with EM_FINDTEXTEX. When the user clicks on Find menuitem, IDM_FIND message is sent and the Find dialog box is displayed.

    invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
    .if eax!=0

When the user types the text to search for and then press OK button, we get the text to be searched for into FindBuffer.

        mov uFlags,0
        invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg

If the text string is not null, we continue to initialize uFlags variable to 0.This variable is used to store the search flags used withEM_FINDTEXTEX. After that, we obtain the current selection with EM_EXGETSEL because we need to know the starting point of the search operation.

    invoke IsDlgButtonChecked,hWnd,IDC_DOWN
        .if eax==BST_CHECKED
            or uFlags,FR_DOWN
            mov eax,findtext.chrg.cpMin
            .if eax!=findtext.chrg.cpMax
                push findtext.chrg.cpMax
                pop findtext.chrg.cpMin
            .endif
            mov findtext.chrg.cpMax,-1
        .else
            mov findtext.chrg.cpMax,0
        .endif

The next part is a little tricky. We check the direction radio button to ascertain which direction the search should go. If the downward search is indicated, we set FR_DOWN flag to uFlags. After that, we check whether a selection is currently in effect by comparing the values of cpMin and cpMax. If both values are not equal, it means there is a current selection and we must continue the search from the end of that selection to the end of text in the control. Thus we need to replace the value of cpMax with that of cpMin and change the value of cpMax to -1 (0FFFFFFFFh). If there is no current selection, the range to search is from the current caret position to the end of text.

If the user chooses to search upward, we use the range from the start of the selection to the beginning of the text in the control. That's why we only modify the value of cpMax to 0. In the case of upward search, cpMin contains the character index of the last character in the search range and cpMax the character index of the first char in the search range. It's the inverse of the downward search.

        invoke IsDlgButtonChecked,hWnd,IDC_MATCHCASE
        .if eax==BST_CHECKED
            or uFlags,FR_MATCHCASE
        .endif
        invoke IsDlgButtonChecked,hWnd,IDC_WHOLEWORD
        .if eax==BST_CHECKED
            or uFlags,FR_WHOLEWORD
        .endif
        mov findtext.lpstrText,offset FindBuffer

We continue to check the checkboxes for the search flags, ie, FR_MATCHCASE and FR_WHOLEWORD. Lastly, we put the offset of the text to search for in lpstrText member.

        invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,uFlags,addr findtext
        .if eax!=-1
            invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
        .endif
    .endif

We are now ready to issue EM_FINDTEXTEX. After that, we examine the search result returned by SendMessage. If the return value is -1, no match is found in the search range. Otherwise, chrgText member of FINDTEXTEX structure is filled with the character indices of the matching text. We thus proceed to select it with EM_EXSETSEL.

The replace operation is done in much the same manner.

    invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
    invoke GetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer,sizeof ReplaceBuffer

We retrieve the text to search for and the text used to replace.

    mov findtext.chrg.cpMin,0
    mov findtext.chrg.cpMax,-1
    mov findtext.lpstrText,offset FindBuffer

To make it easy, the replace operation affects all the text in the control. Thus the starting index is 0 and the ending index is -1.

    mov settext.flags,ST_SELECTION
    mov settext.codepage,CP_ACP

We initialize SETTEXTEX structure to indicate that we want to replace the current selection and use the default system code page.

    .while TRUE
        invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext
        .if eax==-1
            .break
        .else
            invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
            invoke SendMessage,hwndRichEdit,EM_SETTEXTEX,addr settext,addr ReplaceBuffer
        .endif
    .endw

We enter an infinite loop, searching for the matching text. If one is found, we select it with EM_EXSETSEL and replace it with EM_SETTEXTEX. When no more match is found, we exit the loop.

Find Next and Find Prev. features use EM_FINDTEXTEX message in the similar manner to the find operation.

We will examine the Go to Line feature next. When the user clicks Go To Line menuitem, we display a dialog box below:

When the user types a line number and presses Ok button, we begin the operation.

    invoke GetDlgItemInt,hWnd,IDC_LINENO,NULL,FALSE
    mov LineNo,eax

Obtain the line number from the edit control

    invoke SendMessage,hwndRichEdit,EM_GETLINECOUNT,0,0
    .if eax>LineNo

Obtain the number of lines in the control. Check whether the user specifies the line number that is out of the range.

        invoke SendMessage,hwndRichEdit,EM_LINEINDEX,LineNo,0

If the line number is valid, we want to move the caret to the first character of that line. So we send EM_LINEINDEX message to the richedit control. This message returns the character index of the first character in the specified line. We send the line number in wParam and in return, we has the character index.

        invoke SendMessage,hwndRichEdit,EM_SETSEL,eax,eax

To set the current selection, this time we use EM_SETSEL because the character indices are not already in a CHARRANGE structure thus it saves us two instructions (to put those indices into a CHARRANGE structure).

        invoke SetFocus,hwndRichEdit
    .endif

The caret will not be displayed unless the richedit control has the focus. So we call SetFocus on it.

[Iczelion's Win32 Assembly Homepage]


Iczelion's Win32 Assembly Tutorial 35: RichEdit Control: Syntax Hilighting

Tutorial 35: RichEdit Control: Syntax Hilighting

Before reading this tutorial, let me warn you that it's a complicated subject: not suited for a beginner. This is the last in the richedit control tutorials.

Download the example.

Theory

Syntax hilighting is a subject of hot debate for those writing text editors. The best method (in my opinion) is to code a custom edit control and this is the approach taken by lots of commercial softwares. However, for those of us who don't have time for coding such control, the next best thing is to adapt the existing control to make it suit our need.

Let us take a look at what RichEdit control provides to help us in implementing syntax hilighting. I should state at this moment that the following method is not the "correct" path: I just want to show you the pitfall that many fall for. RichEdit control provides EM_SETCHARFORMAT message that you can use to change the color of the text. At first glance, this message seems to be the perfect solution (I know because I was one of the victim). However, a closer examination will show you several things that are undesirable:

  • EM_SETCHARFORMAT only works for a text currently in selection or all text in the control. If you want to change the text color (hilight) a certain word, you must first select it.
  • EM_SETCHARFORMAT is very slow
  • It has a problem with the caret position in the richedit control

With the above discussion, you can see that using EM_SETCHARFORMAT is a wrong choice. I'll show you the "relatively correct" choice.

The method I currently use is "syntax hilighting just-in-time". I'll hilight only the visible portion of text. Thus the speed of the hilighting will not be related to the size of the file at all. No matter how large the file, only a small portion of it is visible at one time.

How to do that? The answer is simple:

  1. subclass the richedit control and handle WM_PAINT message within your own window procedure
  2. When it receives WM_PAINT message, it calls the original window procedure of the richedit control to let it update the screen as usual.
  3. After that, we overwrite the words to be hilighted with different color

Of course, the road is not that easy: there are still a couple of minor things to fix but the above method works quite nicely. The display speed is very satisfactory.

Now let's concentrate on the detail. The subclassing process is simple and doesn't require much attention. The really complicated part is when we have to find a fast way of searching for the words to be hilighted. This is further complicated by the need not to hilight any word within a comment block.

The method I use may not be the best but it works ok. I'm sure you can find a faster way. Anyway, here it is:

  • I create a 256 dword array, initialized to 0. Each dword corresponds to a possible ASCII character,named ASMSyntaxArray. For example, the 21th dword represents the ascii 20h (space). I use them as a fast lookup table: For example, if I have the word "include", I'll extract the first character (i) from the word and look up the dword at the corresponding index. If that dword is 0, I know immediately that there is no words to be hilighted starting with "i". If the dword is non-zero, it contains the pointer to the linked list of the WORDINFO structure which contains the information about the word to be hilighted.
  • I read the words to be hilighted and create a WORDINFO structure for each of them.
            WORDINFO struct
                WordLen dd ? 		; the length of the word: used as a quick comparison
                pszWord dd ? 	; pointer to the word
                pColor dd ? 		; point to the dword that contains the color used to hilite the word
                NextLink dd ? 		; point to the next WORDINFO structure
            WORDINFO ends

As you can see, I use the length of the word as the second quick comparison. If the first character of the word matches, we next compare its length to the available words. Each dword in ASMSyntaxArray contains a pointer to the head of the associated WORDINFO array. For example, the dword that represents the character "i" will contain the pointer to the linked list of the words that begin with "i". pColor member points to the dword that contains the color value used to hilight the word. pszWord points to the word to be hilighted, in lowercase.

  • The memory for the linked list is allocated from the default heap so it's fast and easy to clean up, ie, no cleaning up required at all.

The word list is stored in a file named "wordfile.txt" and I access it with GetPrivateProfileString APIs. I provide as many as 10 different syntax coloring, starting from C1 to C10. The color array is named ASMColorArray. pColor member of each WORDINFO structure points to one of the dwords in ASMColorArray. Thus it is easy to change the syntax coloring on the fly: you just change the dword in ASMColorArray and all words using that color will use the new color immediately.

Example

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\gdi32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

WORDINFO struct
    WordLen dd ?		; the length of the word: used as a quick comparison
    pszWord dd ?		; pointer to the word
    pColor dd ?		; point to the dword that contains the color used to hilite the word
    NextLink dd ?		; point to the next WORDINFO structure
WORDINFO ends

.const
IDR_MAINMENU                   equ 101
IDM_OPEN                      equ  40001
IDM_SAVE                       equ 40002
IDM_CLOSE                      equ 40003
IDM_SAVEAS                     equ 40004
IDM_EXIT                       equ 40005
IDM_COPY                      equ  40006
IDM_CUT                       equ  40007
IDM_PASTE                      equ 40008
IDM_DELETE                     equ 40009
IDM_SELECTALL                  equ 40010
IDM_OPTION 			equ 40011
IDM_UNDO			equ 40012
IDM_REDO			equ 40013
IDD_OPTIONDLG                  equ 101
IDC_BACKCOLORBOX               equ 1000
IDC_TEXTCOLORBOX               equ 1001
IDR_MAINACCEL                 equ  105
IDD_FINDDLG                    equ 102
IDD_GOTODLG                    equ 103
IDD_REPLACEDLG                 equ 104
IDC_FINDEDIT                  equ  1000
IDC_MATCHCASE                  equ 1001
IDC_REPLACEEDIT                 equ 1001
IDC_WHOLEWORD                  equ 1002
IDC_DOWN                       equ 1003
IDC_UP                       equ   1004
IDC_LINENO                   equ   1005
IDM_FIND                       equ 40014
IDM_FINDNEXT                  equ  40015
IDM_REPLACE                     equ 40016
IDM_GOTOLINE                   equ 40017
IDM_FINDPREV                  equ  40018
RichEditID 			equ 300

.data
ClassName db "IczEditClass",0
AppName  db "IczEdit version 3.0",0
RichEditDLL db "riched20.dll",0
RichEditClass db "RichEdit20A",0
NoRichEdit db "Cannot find riched20.dll",0
ASMFilterString 		db "ASM Source code (*.asm)",0,"*.asm",0
                db "All Files (*.*)",0,"*.*",0,0
OpenFileFail db "Cannot open the file",0
WannaSave db "The data in the control is modified. Want to save it?",0
FileOpened dd FALSE
BackgroundColor dd 0FFFFFFh		; default to white
TextColor dd 0		; default to black
WordFileName db "\wordfile.txt",0
ASMSection db "ASSEMBLY",0
C1Key db "C1",0
C2Key db "C2",0
C3Key db "C3",0
C4Key db "C4",0
C5Key db "C5",0
C6Key db "C6",0
C7Key db "C7",0
C8Key db "C8",0
C9Key db "C9",0
C10Key db "C10",0
ZeroString db 0
ASMColorArray dd 0FF0000h,0805F50h,0FFh,666F00h,44F0h,5F8754h,4 dup(0FF0000h)
CommentColor dd 808000h

.data?
hInstance dd ?
hRichEdit dd ?
hwndRichEdit dd ?
FileName db 256 dup(?)
AlternateFileName db 256 dup(?)
CustomColors dd 16 dup(?)
FindBuffer db 256 dup(?)
ReplaceBuffer db 256 dup(?)
uFlags dd ?
findtext FINDTEXTEX <>
ASMSyntaxArray dd 256 dup(?)
hSearch dd ?		; handle to the search/replace dialog box
hAccel dd ?
hMainHeap dd ?		; heap handle
OldWndProc dd ?
RichEditVersion dd ?

.code
start:
    mov byte ptr [FindBuffer],0
    mov byte ptr [ReplaceBuffer],0
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke LoadLibrary,addr RichEditDLL
    .if eax!=0
        mov hRichEdit,eax
        invoke GetProcessHeap
        mov hMainHeap,eax
        call FillHiliteInfo
        invoke WinMain, hInstance,0,0, SW_SHOWDEFAULT
        invoke FreeLibrary,hRichEdit
    .else
        invoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR
    .endif
    invoke ExitProcess,eax

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:DWORD
    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInst
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,IDR_MAINMENU
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
           hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    invoke LoadAccelerators,hInstance,IDR_MAINACCEL
    mov hAccel,eax
    .while TRUE
        invoke GetMessage, ADDR msg,0,0,0
        .break .if (!eax)
        invoke IsDialogMessage,hSearch,addr msg
        .if eax==FALSE
            invoke TranslateAccelerator,hwnd,hAccel,addr msg
            .if eax==0
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .endif
        .endif
    .endw
    mov   eax,msg.wParam
    ret
WinMain endp

StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORD
    invoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0
    xor eax,1
    ret
StreamInProc endp

StreamOutProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesWritten:DWORD
    invoke WriteFile,hFile,pBuffer,NumBytes,pBytesWritten,0
    xor eax,1
    ret
StreamOutProc endp

CheckModifyState proc hWnd:DWORD
    invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
    .if eax!=0
        invoke MessageBox,hWnd,addr WannaSave,addr AppName,MB_YESNOCANCEL
        .if eax==IDYES
            invoke SendMessage,hWnd,WM_COMMAND,IDM_SAVE,0
        .elseif eax==IDCANCEL
            mov eax,FALSE
            ret
        .endif
    .endif
    mov eax,TRUE
    ret
CheckModifyState endp

SetColor proc
    LOCAL cfm:CHARFORMAT
    invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColor
    invoke RtlZeroMemory,addr cfm,sizeof cfm
    mov cfm.cbSize,sizeof cfm
    mov cfm.dwMask,CFM_COLOR
    push TextColor
    pop cfm.crTextColor
    invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfm
    ret
SetColor endp

OptionProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL clr:CHOOSECOLOR
    .if uMsg==WM_INITDIALOG
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDC_BACKCOLORBOX
                invoke RtlZeroMemory,addr clr,sizeof clr
                mov clr.lStructSize,sizeof clr
                push hWnd
                pop clr.hwndOwner
                push hInstance
                pop clr.hInstance
                push BackgroundColor
                pop clr.rgbResult
                mov clr.lpCustColors,offset CustomColors
                mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
                invoke ChooseColor,addr clr
                .if eax!=0
                    push clr.rgbResult
                    pop BackgroundColor
                    invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
                    invoke InvalidateRect,eax,0,TRUE
                .endif
            .elseif ax==IDC_TEXTCOLORBOX
                invoke RtlZeroMemory,addr clr,sizeof clr
                mov clr.lStructSize,sizeof clr
                push hWnd
                pop clr.hwndOwner
                push hInstance
                pop clr.hInstance
                push TextColor
                pop clr.rgbResult
                mov clr.lpCustColors,offset CustomColors
                mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
                invoke ChooseColor,addr clr
                .if eax!=0
                    push clr.rgbResult
                    pop TextColor
                    invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
                    invoke InvalidateRect,eax,0,TRUE
                .endif
            .elseif ax==IDOK
                invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
                push eax
                invoke SetColor
                pop eax
                invoke SendMessage,hwndRichEdit,EM_SETMODIFY,eax,0
                invoke EndDialog,hWnd,0
            .endif
        .endif
    .elseif uMsg==WM_CTLCOLORSTATIC
        invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
        .if eax==lParam
            invoke CreateSolidBrush,BackgroundColor
            ret
        .else
            invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
            .if eax==lParam
                invoke CreateSolidBrush,TextColor
                ret
            .endif
        .endif
        mov eax,FALSE
        ret
    .elseif uMsg==WM_CLOSE
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
OptionProc endp

SearchProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .if uMsg==WM_INITDIALOG
        push hWnd
        pop hSearch
        invoke CheckRadioButton,hWnd,IDC_DOWN,IDC_UP,IDC_DOWN
        invoke SendDlgItemMessage,hWnd,IDC_FINDEDIT,WM_SETTEXT,0,addr FindBuffer
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDOK
                mov uFlags,0
                invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
                invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
                .if eax!=0
                    invoke IsDlgButtonChecked,hWnd,IDC_DOWN
                    .if eax==BST_CHECKED
                        or uFlags,FR_DOWN
                        mov eax,findtext.chrg.cpMin
                        .if eax!=findtext.chrg.cpMax
                            push findtext.chrg.cpMax
                            pop findtext.chrg.cpMin
                        .endif
                        mov findtext.chrg.cpMax,-1
                    .else
                        mov findtext.chrg.cpMax,0
                    .endif
                    invoke IsDlgButtonChecked,hWnd,IDC_MATCHCASE
                    .if eax==BST_CHECKED
                        or uFlags,FR_MATCHCASE
                    .endif
                    invoke IsDlgButtonChecked,hWnd,IDC_WHOLEWORD
                    .if eax==BST_CHECKED
                        or uFlags,FR_WHOLEWORD
                    .endif
                    mov findtext.lpstrText,offset FindBuffer
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,uFlags,addr findtext
                    .if eax!=-1
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                    .endif
                .endif
            .elseif ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .else
                mov eax,FALSE
                ret
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        mov hSearch,0
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
SearchProc endp

ReplaceProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL settext:SETTEXTEX
    .if uMsg==WM_INITDIALOG
        push hWnd
        pop hSearch
        invoke SetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer
        invoke SetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDOK
                invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
                invoke GetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer,sizeof ReplaceBuffer
                mov findtext.chrg.cpMin,0
                mov findtext.chrg.cpMax,-1
                mov findtext.lpstrText,offset FindBuffer
                mov settext.flags,ST_SELECTION
                mov settext.codepage,CP_ACP
                .while TRUE
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext
                    .if eax==-1
                        .break
                    .else
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                        invoke SendMessage,hwndRichEdit,EM_SETTEXTEX,addr settext,addr ReplaceBuffer
                    .endif
                .endw
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        mov hSearch,0
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
ReplaceProc endp

GoToProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL LineNo:DWORD
    LOCAL chrg:CHARRANGE
    .if uMsg==WM_INITDIALOG
        push hWnd
        pop hSearch
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDOK
                invoke GetDlgItemInt,hWnd,IDC_LINENO,NULL,FALSE
                mov LineNo,eax
                invoke SendMessage,hwndRichEdit,EM_GETLINECOUNT,0,0
                .if eax>LineNo
                    invoke SendMessage,hwndRichEdit,EM_LINEINDEX,LineNo,0
                    invoke SendMessage,hwndRichEdit,EM_SETSEL,eax,eax
                    invoke SetFocus,hwndRichEdit
                .endif
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        mov hSearch,0
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
GoToProc endp

PrepareEditMenu proc hSubMenu:DWORD
    LOCAL chrg:CHARRANGE
    invoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0
    .if eax==0		; no text in the clipboard
        invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_ENABLED
    .endif
    invoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0
    .if eax==0
        invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_ENABLED
    .endif
    invoke SendMessage,hwndRichEdit,EM_CANREDO,0,0
    .if eax==0
        invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_ENABLED
    .endif
    invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrg
    mov eax,chrg.cpMin
    .if eax==chrg.cpMax		; no current selection
        invoke EnableMenuItem,hSubMenu,IDM_COPY,MF_GRAYED
        invoke EnableMenuItem,hSubMenu,IDM_CUT,MF_GRAYED
        invoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_COPY,MF_ENABLED
        invoke EnableMenuItem,hSubMenu,IDM_CUT,MF_ENABLED
        invoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_ENABLED
    .endif
    ret
PrepareEditMenu endp

ParseBuffer proc uses edi esi hHeap:DWORD,pBuffer:DWORD, nSize:DWORD, ArrayOffset:DWORD,pArray:DWORD
    LOCAL buffer[128]:BYTE
    LOCAL InProgress:DWORD
    mov InProgress,FALSE
    lea esi,buffer
    mov edi,pBuffer
    invoke CharLower,edi
    mov ecx,nSize
SearchLoop:
    or ecx,ecx
    jz Finished
    cmp byte ptr [edi]," "
    je EndOfWord
    cmp byte ptr [edi],9 	; tab
    je EndOfWord
    mov InProgress,TRUE
    mov al,byte ptr [edi]
    mov byte ptr [esi],al
    inc esi
SkipIt:
    inc edi
    dec ecx
    jmp SearchLoop
EndOfWord:
    cmp InProgress,TRUE
    je WordFound
    jmp SkipIt
WordFound:
    mov byte ptr [esi],0
    push ecx
    invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof WORDINFO
    push esi
    mov esi,eax
    assume esi:ptr WORDINFO
    invoke lstrlen,addr buffer
    mov [esi].WordLen,eax
    push ArrayOffset
    pop [esi].pColor
    inc eax
    invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eax
    mov [esi].pszWord,eax
    mov edx,eax
    invoke lstrcpy,edx,addr buffer
    mov eax,pArray
    movzx edx,byte ptr [buffer]
    shl edx,2		; multiply by 4
    add eax,edx
    .if dword ptr [eax]==0
        mov dword ptr [eax],esi
    .else
        push dword ptr [eax]
        pop [esi].NextLink
        mov dword ptr [eax],esi
    .endif
    pop esi
    pop ecx
    lea esi,buffer
    mov InProgress,FALSE
    jmp SkipIt
Finished:
    .if InProgress==TRUE
        invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof WORDINFO
        push esi
        mov esi,eax
        assume esi:ptr WORDINFO
        invoke lstrlen,addr buffer
        mov [esi].WordLen,eax
        push ArrayOffset
        pop [esi].pColor
        inc eax
        invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eax
        mov [esi].pszWord,eax
        mov edx,eax
        invoke lstrcpy,edx,addr buffer
        mov eax,pArray
        movzx edx,byte ptr [buffer]
        shl edx,2		; multiply by 4
        add eax,edx
        .if dword ptr [eax]==0
            mov dword ptr [eax],esi
        .else
            push dword ptr [eax]
            pop [esi].NextLink
            mov dword ptr [eax],esi
        .endif
        pop esi
    .endif
    ret
ParseBuffer endp

FillHiliteInfo proc uses edi
    LOCAL buffer[1024]:BYTE
    LOCAL pTemp:DWORD
    LOCAL BlockSize:DWORD
    invoke RtlZeroMemory,addr ASMSyntaxArray,sizeof ASMSyntaxArray
    invoke GetModuleFileName,hInstance,addr buffer,sizeof buffer
    invoke lstrlen,addr buffer
    mov ecx,eax
    dec ecx
    lea edi,buffer
    add edi,ecx
    std
    mov al,"\"
    repne scasb
    cld
    inc edi
    mov byte ptr [edi],0
    invoke lstrcat,addr buffer,addr WordFileName
    invoke GetFileAttributes,addr buffer
    .if eax!=-1
        mov BlockSize,1024*10
        invoke HeapAlloc,hMainHeap,0,BlockSize
        mov pTemp,eax
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C1Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C2Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,4
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C3Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,8
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C4Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,12
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C5Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,16
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C6Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,20
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C7Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,24
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C8Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,28
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C9Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,32
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C10Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,36
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
        invoke HeapFree,hMainHeap,0,pTemp
    .endif
    ret
FillHiliteInfo endp

NewRichEditProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL hdc:DWORD
    LOCAL hOldFont:DWORD
    LOCAL FirstChar:DWORD
    LOCAL rect:RECT
    LOCAL txtrange:TEXTRANGE
    LOCAL buffer[1024*10]:BYTE
    LOCAL hRgn:DWORD
    LOCAL hOldRgn:DWORD
    LOCAL RealRect:RECT
    LOCAL pString:DWORD
    LOCAL BufferSize:DWORD
    LOCAL pt:POINT
    .if uMsg==WM_PAINT
        push edi
        push esi
        invoke HideCaret,hWnd
        invoke CallWindowProc,OldWndProc,hWnd,uMsg,wParam,lParam
        push eax
        mov edi,offset ASMSyntaxArray
        invoke GetDC,hWnd
        mov hdc,eax
        invoke SetBkMode,hdc,TRANSPARENT
        invoke SendMessage,hWnd,EM_GETRECT,0,addr rect
        invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect
        invoke SendMessage,hWnd,EM_LINEFROMCHAR,eax,0
        invoke SendMessage,hWnd,EM_LINEINDEX,eax,0
        mov txtrange.chrg.cpMin,eax
        mov FirstChar,eax
        invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect.right
        mov txtrange.chrg.cpMax,eax
        push rect.left
        pop RealRect.left
        push rect.top
        pop RealRect.top
        push rect.right
        pop RealRect.right
        push rect.bottom
        pop RealRect.bottom
        invoke CreateRectRgn,RealRect.left,RealRect.top,RealRect.right,RealRect.bottom
        mov hRgn,eax
        invoke SelectObject,hdc,hRgn
        mov hOldRgn,eax
        invoke SetTextColor,hdc,CommentColor
        lea eax,buffer
        mov txtrange.lpstrText,eax
        invoke SendMessage,hWnd,EM_GETTEXTRANGE,0,addr txtrange
        .if eax>0
            mov esi,eax		; esi == size of the text
            mov BufferSize,eax
            push edi
            push ebx
            lea edi,buffer
            mov edx,edi		; used as the reference point
            mov ecx,esi
            mov al,";"
ScanMore:
            repne scasb
            je NextSkip
            jmp NoMoreHit
NextSkip:
            dec edi
            inc ecx
            mov pString,edi
            mov ebx,edi
            sub ebx,edx
            add ebx,FirstChar
            mov txtrange.chrg.cpMin,ebx
            push eax
            mov al,0Dh
            repne scasb
            pop eax
HiliteTheComment:
            .if ecx>0
                mov byte ptr [edi-1],0
            .endif
            mov ebx,edi
            sub ebx,edx
            add ebx,FirstChar
            mov txtrange.chrg.cpMax,ebx
            pushad
            mov edi,pString
            mov esi,txtrange.chrg.cpMax
            sub esi,txtrange.chrg.cpMin		; esi contains the length of the buffer
            mov eax,esi
            push edi
            .while eax>0
                .if byte ptr [edi]==9
                    mov byte ptr [edi],0
                .endif
                inc edi
                dec eax
            .endw
            pop edi
            .while esi>0
                .if byte ptr [edi]!=0
                    invoke lstrlen,edi
                    push eax
                    mov ecx,edi
                    lea edx,buffer
                    sub ecx,edx
                    add ecx,FirstChar
                    .if RichEditVersion==3
                        invoke SendMessage,hWnd,EM_POSFROMCHAR,addr rect,ecx
                    .else
                        invoke SendMessage,hWnd,EM_POSFROMCHAR,ecx,0
                        mov ecx,eax
                        and ecx,0FFFFh
                        mov rect.left,ecx
                        shr eax,16
                        mov rect.top,eax
                    .endif
                    invoke DrawText,hdc,edi,-1,addr rect,0
                    pop eax
                    add edi,eax
                    sub esi,eax
                .else
                    inc edi
                    dec esi
                .endif
            .endw
            mov ecx,txtrange.chrg.cpMax
            sub ecx,txtrange.chrg.cpMin
            invoke RtlZeroMemory,pString,ecx
            popad
            .if ecx>0
                jmp ScanMore
            .endif
NoMoreHit:
            pop ebx
            pop edi
            mov ecx,BufferSize
            lea esi,buffer
            .while ecx>0
                mov al,byte ptr [esi]
                .if al==" " || al==0Dh || al=="/" || al=="," || al=="|" || al=="+" || al=="-" || al=="*" || al=="&" || al=="<" || al==">" || al=="=" || al=="(" || al==")" || al=="{" || al=="}" || al=="[" || al=="]" || al=="^" || al==":" || al==9
                    mov byte ptr [esi],0
                .endif
                dec ecx
                inc esi
            .endw
            lea esi,buffer
            mov ecx,BufferSize
            .while ecx>0
                mov al,byte ptr [esi]
                .if al!=0
                    push ecx
                    invoke lstrlen,esi
                    push eax
                    mov edx,eax		; edx contains the length of the string
                    movzx eax,byte ptr [esi]
                    .if al>="A" && al<="Z"
                        sub al,"A"
                        add al,"a"
                    .endif
                    shl eax,2
                    add eax,edi		; edi contains the pointer to the WORDINFO pointer array
                    .if dword ptr [eax]!=0
                        mov eax,dword ptr [eax]
                        assume eax:ptr WORDINFO
                        .while eax!=0
                            .if edx==[eax].WordLen
                                pushad
                                invoke lstrcmpi,[eax].pszWord,esi
                                .if eax==0
                                    popad
                                    mov ecx,esi
                                    lea edx,buffer
                                    sub ecx,edx
                                    add ecx,FirstChar
                                    pushad
                                    .if RichEditVersion==3
                                        invoke SendMessage,hWnd,EM_POSFROMCHAR,addr rect,ecx
                                    .else
                                        invoke SendMessage,hWnd,EM_POSFROMCHAR,ecx,0
                                        mov ecx,eax
                                        and ecx,0FFFFh
                                        mov rect.left,ecx
                                        shr eax,16
                                        mov rect.top,eax
                                    .endif
                                    popad
                                    mov edx,[eax].pColor
                                    invoke SetTextColor,hdc,dword ptr [edx]
                                    invoke DrawText,hdc,esi,-1,addr rect,0
                                    .break
                                .endif
                                popad
                            .endif
                            push [eax].NextLink
                            pop eax
                        .endw
                    .endif
                    pop eax
                    pop ecx
                    add esi,eax
                    sub ecx,eax
                .else
                    inc esi
                    dec ecx
                .endif
            .endw
        .endif
        invoke SelectObject,hdc,hOldRgn
        invoke DeleteObject,hRgn
        invoke SelectObject,hdc,hOldFont
        invoke ReleaseDC,hWnd,hdc
        invoke ShowCaret,hWnd
        pop eax
        pop esi
        pop edi
        ret
    .elseif uMsg==WM_CLOSE
        invoke SetWindowLong,hWnd,GWL_WNDPROC,OldWndProc
    .else
        invoke CallWindowProc,OldWndProc,hWnd,uMsg,wParam,lParam
        ret
    .endif
NewRichEditProc endp

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL ofn:OPENFILENAME
    LOCAL buffer[256]:BYTE
    LOCAL editstream:EDITSTREAM
    LOCAL hFile:DWORD
    LOCAL hPopup:DWORD
    LOCAL pt:POINT
    LOCAL chrg:CHARRANGE
    .if uMsg==WM_CREATE
        invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL,\
                CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0
        mov hwndRichEdit,eax
        invoke SendMessage,hwndRichEdit,EM_SETTYPOGRAPHYOPTIONS,TO_SIMPLELINEBREAK,TO_SIMPLELINEBREAK
        invoke SendMessage,hwndRichEdit,EM_GETTYPOGRAPHYOPTIONS,1,1
        .if eax==0		; means this message is not processed
            mov RichEditVersion,2
        .else
            mov RichEditVersion,3
            invoke SendMessage,hwndRichEdit,EM_SETEDITSTYLE,SES_EMULATESYSEDIT,SES_EMULATESYSEDIT
        .endif
        invoke SetWindowLong,hwndRichEdit,GWL_WNDPROC, addr NewRichEditProc
        mov OldWndProc,eax
        invoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0
        invoke SetColor
        invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
        invoke SendMessage,hwndRichEdit,EM_SETEVENTMASK,0,ENM_MOUSEEVENTS
        invoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0
    .elseif uMsg==WM_NOTIFY
        push esi
        mov esi,lParam
        assume esi:ptr NMHDR
        .if [esi].code==EN_MSGFILTER
            assume esi:ptr MSGFILTER
            .if [esi].msg==WM_RBUTTONDOWN
                invoke GetMenu,hWnd
                invoke GetSubMenu,eax,1
                mov hPopup,eax
                invoke PrepareEditMenu,hPopup
                mov edx,[esi].lParam
                mov ecx,edx
                and edx,0FFFFh
                shr ecx,16
                mov pt.x,edx
                mov pt.y,ecx
                invoke ClientToScreen,hWnd,addr pt
                invoke TrackPopupMenu,hPopup,TPM_LEFTALIGN or TPM_BOTTOMALIGN,pt.x,pt.y,NULL,hWnd,NULL
            .endif
        .endif
        pop esi
    .elseif uMsg==WM_INITMENUPOPUP
        mov eax,lParam
        .if ax==0		; file menu
            .if FileOpened==TRUE	; a file is already opened
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED
            .else
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED
            .endif
        .elseif ax==1	; edit menu
            invoke PrepareEditMenu,wParam
        .elseif ax==2		; search menu bar
            .if FileOpened==TRUE
                invoke EnableMenuItem,wParam,IDM_FIND,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_FINDPREV,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_REPLACE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_ENABLED
            .else
                invoke EnableMenuItem,wParam,IDM_FIND,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_FINDPREV,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_REPLACE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_GRAYED
            .endif
        .endif
    .elseif uMsg==WM_COMMAND
        .if lParam==0		; menu commands
            mov eax,wParam
            .if ax==IDM_OPEN
                invoke RtlZeroMemory,addr ofn,sizeof ofn
                mov ofn.lStructSize,sizeof ofn
                push hWnd
                pop ofn.hwndOwner
                push hInstance
                pop ofn.hInstance
                mov ofn.lpstrFilter,offset ASMFilterString
                mov ofn.lpstrFile,offset FileName
                mov byte ptr [FileName],0
                mov ofn.nMaxFile,sizeof FileName
                mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
                invoke GetOpenFileName,addr ofn
                .if eax!=0
                    invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
                    .if eax!=INVALID_HANDLE_VALUE
                        mov hFile,eax
                        mov editstream.dwCookie,eax
                        mov editstream.pfnCallback,offset StreamInProc
                        invoke SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream
                        invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
                        invoke CloseHandle,hFile
                        mov FileOpened,TRUE
                    .else
                        invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
                    .endif
                .endif
            .elseif ax==IDM_CLOSE
                invoke CheckModifyState,hWnd
                .if eax==TRUE
                    invoke SetWindowText,hwndRichEdit,0
                    mov FileOpened,FALSE
                .endif
            .elseif ax==IDM_SAVE
                invoke CreateFile,addr FileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
                .if eax!=INVALID_HANDLE_VALUE
@@:
                    mov hFile,eax
                    mov editstream.dwCookie,eax
                    mov editstream.pfnCallback,offset StreamOutProc
                    invoke SendMessage,hwndRichEdit,EM_STREAMOUT,SF_TEXT,addr editstream
                    invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
                    invoke CloseHandle,hFile
                .else
                    invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
                .endif
            .elseif ax==IDM_COPY
                invoke SendMessage,hwndRichEdit,WM_COPY,0,0
            .elseif ax==IDM_CUT
                invoke SendMessage,hwndRichEdit,WM_CUT,0,0
            .elseif ax==IDM_PASTE
                invoke SendMessage,hwndRichEdit,WM_PASTE,0,0
            .elseif ax==IDM_DELETE
                invoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0
            .elseif ax==IDM_SELECTALL
                mov chrg.cpMin,0
                mov chrg.cpMax,-1
                invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg
            .elseif ax==IDM_UNDO
                invoke SendMessage,hwndRichEdit,EM_UNDO,0,0
            .elseif ax==IDM_REDO
                invoke SendMessage,hwndRichEdit,EM_REDO,0,0
            .elseif ax==IDM_OPTION
                invoke DialogBoxParam,hInstance,IDD_OPTIONDLG,hWnd,addr OptionProc,0
            .elseif ax==IDM_SAVEAS
                invoke RtlZeroMemory,addr ofn,sizeof ofn
                mov ofn.lStructSize,sizeof ofn
                push hWnd
                pop ofn.hwndOwner
                push hInstance
                pop ofn.hInstance
                mov ofn.lpstrFilter,offset ASMFilterString
                mov ofn.lpstrFile,offset AlternateFileName
                mov byte ptr [AlternateFileName],0
                mov ofn.nMaxFile,sizeof AlternateFileName
                mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
                invoke GetSaveFileName,addr ofn
                .if eax!=0
                    invoke CreateFile,addr AlternateFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
                    .if eax!=INVALID_HANDLE_VALUE
                        jmp @B
                    .endif
                .endif
            .elseif ax==IDM_FIND
                .if hSearch==0
                    invoke CreateDialogParam,hInstance,IDD_FINDDLG,hWnd,addr SearchProc,0
                .endif
            .elseif ax==IDM_REPLACE
                .if hSearch==0
                    invoke CreateDialogParam,hInstance,IDD_REPLACEDLG,hWnd,addr ReplaceProc,0
                .endif
            .elseif ax==IDM_GOTOLINE
                .if hSearch==0
                    invoke CreateDialogParam,hInstance,IDD_GOTODLG,hWnd,addr GoToProc,0
                .endif
            .elseif ax==IDM_FINDNEXT
                invoke lstrlen,addr FindBuffer
                .if eax!=0
                    invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
                    mov eax,findtext.chrg.cpMin
                    .if eax!=findtext.chrg.cpMax
                        push findtext.chrg.cpMax
                        pop findtext.chrg.cpMin
                    .endif
                    mov findtext.chrg.cpMax,-1
                    mov findtext.lpstrText,offset FindBuffer
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext
                    .if eax!=-1
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                    .endif
                .endif
            .elseif ax==IDM_FINDPREV
                invoke lstrlen,addr FindBuffer
                .if eax!=0
                    invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
                    mov findtext.chrg.cpMax,0
                    mov findtext.lpstrText,offset FindBuffer
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,0,addr findtext
                    .if eax!=-1
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                    .endif
                .endif
            .elseif ax==IDM_EXIT
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        invoke CheckModifyState,hWnd
        .if eax==TRUE
            invoke DestroyWindow,hWnd
        .endif
    .elseif uMsg==WM_SIZE
        mov eax,lParam
        mov edx,eax
        and eax,0FFFFh
        shr edx,16
        invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE
    .elseif uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .else
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
WndProc endp
end start

Analysis:

The first action before calling WinMain to to call FillHiliteInfo. This function reads the content of wordfile.txt and parses the content.

FillHiliteInfo proc uses edi
    LOCAL buffer[1024]:BYTE
    LOCAL pTemp:DWORD
    LOCAL BlockSize:DWORD
    invoke RtlZeroMemory,addr ASMSyntaxArray,sizeof ASMSyntaxArray

Initialize ASMSyntaxArray to zero.

    invoke GetModuleFileName,hInstance,addr buffer,sizeof buffer
    invoke lstrlen,addr buffer
    mov ecx,eax
    dec ecx
    lea edi,buffer
    add edi,ecx
    std
    mov al,"\"
    repne scasb
    cld
    inc edi
    mov byte ptr [edi],0
    invoke lstrcat,addr buffer,addr WordFileName

Construct the full path name of wordfile.txt: I assume that it's always in the same folder as the program.

    invoke GetFileAttributes,addr buffer
    .if eax!=-1

I use this method as a quick way of checking whether a file exists.

        mov BlockSize,1024*10
        invoke HeapAlloc,hMainHeap,0,BlockSize
        mov pTemp,eax

Allocate the memory block to store the words. Default to 10K. The memory is allocated from the default heap.

@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C1Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0

I use GetPrivateProfileString to retrieve the content of each key in wordfile.txt. The key starts from C1 to C10.

            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif

Checking whether the memory block is large enough. If it is not, we increment the size by 10K until the block is large enough.

            mov edx,offset ASMColorArray
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray

Pass the words, the memory block handle, the size of the data read from wordfile.txt, the address of the color dword that will be used to hilight the words and the address of ASMSyntaxArray.

Now, let's examine what ParseBuffer does. In essence, this function accepts the buffer containing the words to be hilighted ,parses them to individual words and stores each of them in a WORDINFO structure array that can be accessed quickly from ASMSyntaxArray.

ParseBuffer proc uses edi esi hHeap:DWORD,pBuffer:DWORD, nSize:DWORD, ArrayOffset:DWORD,pArray:DWORD
    LOCAL buffer[128]:BYTE
    LOCAL InProgress:DWORD
    mov InProgress,FALSE

InProgress is the flag I use to indicate whether the scanning process has begun. If the value is FALSE, we haven't encountered a non-white space character yet.

    lea esi,buffer
    mov edi,pBuffer
    invoke CharLower,edi

esi points to our local buffer that will contain the word we have parsed from the word list. edi points to the word list string. To simplify the search later, we convert all characters to lowercase.

    mov ecx,nSize
SearchLoop:
    or ecx,ecx
    jz Finished
    cmp byte ptr [edi]," "
    je EndOfWord
    cmp byte ptr [edi],9 	; tab
    je EndOfWord

Scan the whole word list in the buffer, looking for the white spaces. If a white space is found, we have to determine whether it marks the end or the beginning of a word.

    mov InProgress,TRUE
    mov al,byte ptr [edi]
    mov byte ptr [esi],al
    inc esi
SkipIt:
    inc edi
    dec ecx
    jmp SearchLoop

If the byte under scrutiny is not a white space, we copy it to the buffer to construct a word and then continue the scan.

EndOfWord:
    cmp InProgress,TRUE
    je WordFound
    jmp SkipIt

If a white space is found, we check the value in InProgress. If the value is TRUE, we can assume that the white space marks the end of a word and we may proceed to put the word currently in the local buffer (pointed to by esi) into a WORDINFO structure. If the value is FALSE, we continue the scan until a non-white space character is found.

WordFound:
    mov byte ptr [esi],0
    push ecx
    invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof WORDINFO

When the end of a word is found, we append 0 to the buffer to make the word an ASCIIZ string. We then allocate a block of memory from the heap the size of WORDINFO for this word.

    push esi
    mov esi,eax
    assume esi:ptr WORDINFO
    invoke lstrlen,addr buffer
    mov [esi].WordLen,eax

We obtain the length of the word in the local buffer and store it in the WordLen member of the WORDINFO structure, to be used as a quick comparison.

    push ArrayOffset
    pop [esi].pColor

Store the address of the dword that contains the color to be used to hilight the word in pColor member.

    inc eax
    invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eax
    mov [esi].pszWord,eax
    mov edx,eax
    invoke lstrcpy,edx,addr buffer

Allocate memory from the heap to store the word itself. Right now, the WORDINFO structure is ready to be inserted into the appropriate linked list.

    mov eax,pArray
    movzx edx,byte ptr [buffer]
    shl edx,2		; multiply by 4
    add eax,edx

pArray contains the address of ASMSyntaxArray. We want to move to the dword that has the same index as the value of the first character of the word. So we put the first character of the word in edx then multiply edx by 4 (because each element in ASMSyntaxArray is 4 bytes in size) and then add the offset to the address of ASMSyntaxArray. We have the address of the corresponding dword in eax.

    .if dword ptr [eax]==0
        mov dword ptr [eax],esi
    .else
        push dword ptr [eax]
        pop [esi].NextLink
        mov dword ptr [eax],esi
    .endif

Check the value of the dword. If it's 0, it means there is currently no word that begins with this character in the list. We thus put the address of the current WORDINFO structure in that dword.

If the value in the dword is not 0, it means there is at least one word that begins with this character in the array. We thus insert this WORDINFO structure to the head of the linked list and update its NextLink member to point to the next WORDINFO structure.

    pop esi
    pop ecx
    lea esi,buffer
    mov InProgress,FALSE
    jmp SkipIt

After the operation is complete, we begin the next scan cycle until the end of buffer is reached.

        invoke SendMessage,hwndRichEdit,EM_SETTYPOGRAPHYOPTIONS,TO_SIMPLELINEBREAK,TO_SIMPLELINEBREAK
        invoke SendMessage,hwndRichEdit,EM_GETTYPOGRAPHYOPTIONS,1,1
        .if eax==0		; means this message is not processed
            mov RichEditVersion,2
        .else
            mov RichEditVersion,3
            invoke SendMessage,hwndRichEdit,EM_SETEDITSTYLE,SES_EMULATESYSEDIT,SES_EMULATESYSEDIT
        .endif

After the richedit control is created, we need to determine the its version. This step is necessary since EM_POSFROMCHAR behaves differently for RichEdit 2.0 and 3.0 and EM_POSFROMCHAR is crucial to our syntax hilighting routine. I have never seen a documented way of checking the version of richedit control thus I have to use a workaround. In this case, I set an option that is specific to version 3.0 and immediately retrieve its value. If I can retrieve the value, I assume that the control version is 3.0.

If you use RichEdit control version 3.0, you will notice that updating the font color for a large file takes quite a long time. This problem seems to be specific to version 3.0. I found a workaround: making the control emulate the behavior of the system edit control by sending EM_SETEDITSTYLE message.

After we can obtain the version information, we proceed to subclass the richedit control. We will now examine the new window procedure for the richedit control.

NewRichEditProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    ........
    .......
    .if uMsg==WM_PAINT
        push edi
        push esi
        invoke HideCaret,hWnd
        invoke CallWindowProc,OldWndProc,hWnd,uMsg,wParam,lParam
        push eax

We handle WM_PAINT message. First, we hide the caret so as to avoid some ugly gfx after the hilighting. After that we pass the message to the original richedit procedure to let it update the window. When CallWindowProc returns, the text is updated with its usual color/background. Now is our opportunity to do syntax hilighting.

        mov edi,offset ASMSyntaxArray
        invoke GetDC,hWnd
        mov hdc,eax
        invoke SetBkMode,hdc,TRANSPARENT

Store the address of ASMSyntaxArray in edi. Then we obtain the handle to the device context and set the text background mode to transparent so the text that we will write will use the default background color.

        invoke SendMessage,hWnd,EM_GETRECT,0,addr rect
        invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect
        invoke SendMessage,hWnd,EM_LINEFROMCHAR,eax,0
        invoke SendMessage,hWnd,EM_LINEINDEX,eax,0

We want to obtain the visible text so we first have to obtain the formatting rectangle by sending EM_GETRECT message to the richedit control. Now that we have the bounding rectangle, we obtain the nearest character index to the upper left corner of the rectangle with EM_CHARFROMPOS. Once we have the character index (the first visible character in the control), we can start to do syntax hilighting starting from that position. But the effect might not be as good as when we start from the first character of the line that the character is in. That's why I need to obtain the line number of that the first visible character is in by sending EM_LINEFROMCHAR message. To obtain the first character of that line, I send EM_LINEINDEX message.

        mov txtrange.chrg.cpMin,eax
        mov FirstChar,eax
        invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect.right
        mov txtrange.chrg.cpMax,eax

Once we have the first character index, store it for future reference in FirstChar variable. Next we obtain the last visible character index by sending EM_CHARFROMPOS, passing the lower-right corner of the formatting rectangle in lParam.

        push rect.left
        pop RealRect.left
        push rect.top
        pop RealRect.top
        push rect.right
        pop RealRect.right
        push rect.bottom
        pop RealRect.bottom
        invoke CreateRectRgn,RealRect.left,RealRect.top,RealRect.right,RealRect.bottom
        mov hRgn,eax
        invoke SelectObject,hdc,hRgn
        mov hOldRgn,eax

While doing syntax hilighting, I noticed an unsightly side-effect of this method: if the richedit control has a margin (you can specify margin by sending EM_SETMARGINS message to the richedit control), DrawText writes over the margin. Thus I need to create a clipping region, the size of the formatting rectangle, by calling CreateRectRgn. The output of GDI functions will be clipped to the "writable" area.

Next, we need to hilight the comments first and get them out of our way. My method is to search for ";" and hilight the text with the comment color until the carriage return is found. I will not analyze the routine here: it's fairly long and complicated. Suffice here to say that, when all the comments are hilighted, we replace them with 0s in the buffer so that the words in the comments will not be processed/hilighted later.

        mov ecx,BufferSize
        lea esi,buffer
        .while ecx>0
            mov al,byte ptr [esi]
            .if al==" " || al==0Dh || al=="/" || al=="," || al=="|" || al=="+" || al=="-" || al=="*" || al=="&" || al=="<" || al==">" || al=="=" || al=="(" || al==")" || al=="{" || al=="}" || al=="[" || al=="]" || al=="^" || al==":" || al==9
                mov byte ptr [esi],0
            .endif
            dec ecx
            inc esi
        .endw

Once the comments are out of our way, we separate the words in the buffer by replacing the "separator" characters with 0s. With this method, we need not concern about the separator characters while processing the words in the buffer anymore: there is only one separator character, NULL.

        lea esi,buffer
        mov ecx,BufferSize
        .while ecx>0
            mov al,byte ptr [esi]
            .if al!=0

Search the buffer for the first character that is not null,ie, the first character of a word.

                push ecx
                invoke lstrlen,esi
                push eax
                mov edx,eax

Obtain the length of the word and put it in edx

                movzx eax,byte ptr [esi]
                .if al>="A" && al<="Z"
                    sub al,"A"
                    add al,"a"
                .endif

Convert the character to lowercase (if it's an uppercase character)

                shl eax,2
                add eax,edi		; edi contains the pointer to the WORDINFO pointer array
                .if dword ptr [eax]!=0

After that, we skip to the corresponding dword in ASMSyntaxArray and check whether the value in that dword is 0. If it is, we can skip to the next word.

                    mov eax,dword ptr [eax]
                    assume eax:ptr WORDINFO
                    .while eax!=0
                        .if edx==[eax].WordLen

If the value in the dword is non-zero, it points to the linked list of WORDINFO structures. We process to walk the linked list, comparing the length of the word in our local buffer with the length of the word in the WORDINFO structure. This is a quick test before we compare the words. Should save some clock cycles.

                            pushad
                            invoke lstrcmpi,[eax].pszWord,esi
                            .if eax==0

If the lengths of both words are equal, we proceed to compare them with lstrcmpi.

                                popad
                                mov ecx,esi
                                lea edx,buffer
                                sub ecx,edx
                                add ecx,FirstChar

We construct the character index from the address of the first character of the matching word in the buffer. We first obtain its relative offset from the starting address of the buffer then add the character index of the first visible character to it.

                                pushad
                                .if RichEditVersion==3
                                    invoke SendMessage,hWnd,EM_POSFROMCHAR,addr rect,ecx
                                .else
                                    invoke SendMessage,hWnd,EM_POSFROMCHAR,ecx,0
                                    mov ecx,eax
                                    and ecx,0FFFFh
                                    mov rect.left,ecx
                                    shr eax,16
                                    mov rect.top,eax
                                .endif
                                popad

Once we know the character index of the first character of the word to be hilighted, we proceed to obtain the coordinate of it by sending EM_POSFROMCHAR message. However, this message is interpreted differently by richedit 2.0 and 3.0. For richedit 2.0, wParam contains the character index and lParam is not used. It returns the coordinate in eax. For richedit 3.0, wParam is the pointer to a POINT structure that will be filled with the coordinate and lParam contains the character index.

As you can see, passing the wrong arguments to EM_POSFROMCHAR can wreak havoc to your system. That's why I have to differentiate between RichEdit control versions.

                                mov edx,[eax].pColor
                                invoke SetTextColor,hdc,dword ptr [edx]
                                invoke DrawText,hdc,esi,-1,addr rect,0

Once we got the coordinate to start, we set the text color with the one specified in the WORDINFO structure. And then proceed to overwrite the word with the new color.

As the final words, this method can be improved in several ways. For example, I obtain all the text starting from the first to the last visible line. If the lines are very long, the performance may hurt by processing the words that are not visible. You can optimize this by obtaining the really visible text line by line. Also the searching algorithm can be improved by using a more efficient method. Don't take me wrong: the syntax hilighting method used in this example is FAST but it can be FASTER. :)

[Iczelion's Win32 Assembly Homepage]


Version 1.2
(Décembre 2000)

Index

A lire absolument : Le Principe Général

Assembler une DLL

AIDE

Voici le contenu de chacun des Tutoials.

Tut1 : Les Bases
Tut2 : Les boîtes de message. (MessageBox)
Tut3 : Les boîtes de dialogue. (DialogBox)
Tut4 : Dessiner du texte. (Painting with Text)
Tut5 : Encore plus à propos des textes.(Taille, police...)
Tut6 : Les entrées clavier. (Saisie de texte)
Tut7 : Le contrôle de la Souris.
Tut8 : Le menu.
Tut9 : Child Window Controls.
Tut10 : Boîte de dialogue comme Fenêtre Principale.
Tut11 : Encore plus à propos des boîtes de dialogue.
Tut12 : Gestion de la mémoire et des fichiers d'entrée-sortie.
Tut13 : Fichiers à Mémoire Configurée. (File Mapping)
Tut14 : Process.
Tut15 : Programmation Multi-liens.
Tut16 : Event Object.
Tut17 : Les DLL ou Bibliothèques de Liaison, Dynamiques.
Tut18 : Les Controls Communs (Common Controls).
Tut19 : Dossiers organisés sous la forme d'un arbre.
Tut20 : Les Sousclassifications de Fenêtre.
Tut21 : Le Pipe.
Tut22 : Superclassification.
Tut23 : Icône de Barre de Taches.
Tut24 : Windows Hooks.
Tut25 : Un Simple Bitmap.
Tut26 : Splash Screen.
Tut27 : Les Bulles d'Information (Tooltip Control).
Tut28 : Win32 Debug API Partie 1/3.
Tut29 : Win32 Debug API Partie 2/3.
Tut30 : Win32 Debug API Partie 3/3.
Tut31 : Fichiers organisés sous la forme d'une liste.

REMARQUE:

Dans cette nouvelle version j'ai supprimé la partie concernant les APIs. En fait je les ai dissociés de ce ZIP pour les placer dans un autre ZIP qui leur est spécialement dédiés. Bon nombre d'APIs ont été rajoutées. La principale nouveauté, c'est une petite partie concernant la création de DLL, avec un petit programme pour les assembler (Assembles_DLL.bat)

Voilà les Tuts 28, 29 et 30 à propos des debuggers ont étés mis à jour dans la version précédente. Si c'est seulement l'ASM qui vous intéresse, ça n'aura pas beaucoup d'intérêt pour vous, mais par contre si votre truc c'est de cracker et franchement on y prend plaisir, alors ces trois là sont vraiment le but ultime de cette série. Faites un tour aussi sur l'excellent site d'ACZELION (l'adresse est sur chacun ces tuts) vous y en retrouverez encore d'autres toujours sur Masm mais à propos de sujets bien plus pointus. Genre: les VxD, et détection de debuggers en tous genres. (en anglais, s'il vous plait)

MESSAGE POUR TOUS : rien ne change, toujours dans le but de faire évoluer cette doc, je permet à chacun qui le souhaite de la retoucher et faire évoluer les versions pour corriger mes erreurs, mes imprécisions et autres.

Morgatte@citeweb.net


Iczelion's Win32asm tutorial part 1: The Basic

Tutorial 1 : les bases

Ce tutorial suppose que le lecteur sache comment employer MASM. Si vous n'êtes pas familiers avec MASM, téléchargez win32asm.exe et étudiez le texte à l'intérieur du ZIP avant la suite des Tutoials. Bon. Vous êtes maintenant prêts. On y va!

Théorie:

Les programmes Win32 fonctionnent en mode protégé depuis le 80286. Mais le 80286 c'est maintenant de l'histoire ancienne. Donc nous devons seulement nous intéresser au 80386 et à ses descendants. Windows dirigent chaque programme Win32 séparément. Ceci signifie que chaque programme Win32 aura à sa disposition ses propres espaces de mémoire. Cependant, cela ne signifie pas que chaque programme win32 a 4GB de mémoire physique, mais seulement que le programme peut adresser n'importe quelle adresse dans cette gamme. Windows fera tout le nécessaire pour adresser les vraies références (physiques) du programme aux adresses (mémoires) valables 00401000… qu'il utilise. Bien sûr, le programme doit respecter les règles de Windows, autrement il causera une Erreur de Protection Générale tant redoutée. Chaque programme est seul dans son espace d'adresse. C'est la différence avec les programmes Win16. Tous les programmes Win16 peuvent *malheureusement plus ou moins se chevaucher* les uns les l'autres. Mais pas sous Win32. Ces gardes fous réduisent la chance d'une écriture du code d'un programme par dessus les données d'un autre.
Le modèle de mémoire est aussi résolument différent des vieux jours du monde du 16 bits. Désormais, sous Win32, nous ne sommes plus concernés par le modèle de mémoire en plusieurs segments! (0001.xxxx puis 0002.xxxx puis 0003.xxxx ….)Il y a seulement un seul modèle de mémoire : le modèle de mémoire uniforme (Flat). Il n'y a plus de segments de plus 64Ko (différence *.com / *.exe). La mémoire est un seul espace continu. Ça veut dire aussi que vous n'avez plus besoin de jouer avec les différents registres concernant les segments [ES et SS]. Vous pouvez employer n'importe quel registre de segment pour adresser n'importe quel point dans l'espace de mémoire (ouf !). C'est une GRANDE aide pour les programmeurs. C'est ce qui fait de l'assembleur Win32 une programmation aussi facile que le C.
Quand vous programmez sous Win32, vous devez respecter quelques règles importantes. Une règle{*Majeure*} est que Windows emploie lui-même ESI, EDI, EBP et EBX et les valeurs de ses registres changent, en même temps qu'une autre application tourne en parallèle avec Windows. Rappelez-vous donc cette règle {*Primordiale*} d'abord : si vous employez chacun de ces quatre registres à l'intérieur d'une procédure, n'oubliez jamais de les reconstituer{*rétablir*} avant le contrôle de retour à Windows. L'exemple évident sont les Call qui appellent une API de Windows. Cela ne signifie pas que vous ne pouvez pas employer ces quatre registres, vous le pouvez. Mais seulement que vous devez rétablir ces registres après un call.

Contenu:

Voici le squelette de n'importe quel programme. Si vous ne comprenez pas quelques-uns des codes, ne paniquez pas. J'expliquerai chacun d'entre eux plus tard.

.386
.MODEL Flat, STDCALL
.DATA
<Vous initialisez les data>
......
.DATA?
<Vous initialisez les data>
......
.CONST
<Vous initialisez vos constantes>
......
.CODE
<label>
<Vous initialisez votre code(début du segment Code)>
.....
end <label>

C'est tout! On va analyser le squelette de ce programme.

.386
C'est une directive d'assembleur, disant à l'assembleur d'employer le jeu d'instructions du 80386. Vous pouvez aussi employer .486 .586 mais l'important c'est d'utiliser au minimum les fonctions à partir du .386. Il y a en réalité deux formes presque identiques pour chaque modèle de directive. .386/.386p.486/.486p. Ces versions "p" sont nécessaires seulement quand votre programme emploie des instructions privilégiées. Les instructions privilégiées sont des instructions réservées par le système du CPU / FONCTIONNEMENT en mode protégé. Elles peuvent seulement être employés par le code privilégié, comme les conducteurs de dispositif virtuels. La plupart du temps, votre programme travaillera dans le mode non-privilégié donc c'est presque sûr que vous n'emploierez pas les versions -p.

.MODEL FLAT, STDCALL
.MODEL est une directive d'assembleur qui spécifie le type de mémoire de votre programme. Sous Win32, il y a seulement un type, le modèle FLAT (mémoire un en seul bloque).
STDCALL dit à MASM quel est l'ordre de passage des paramètres de la pile vers un Call. De " gauche à droite " ou " le droit à gauche " et aussi ça équilibrera l'encadrement de la pile après l'appel de la fonction.
Sous Win16, il y a deux types de convention d'appel, la convention C et le PASCAL
La convention C passe les paramètres de la pile de droit à gauche, c'est-à-dire le paramètre extrême droit est poussé d'abord. L'interlocuteur est responsable du balancement de l'encadrement de pile après l'appel. Par exemple, pour appeler une fonction nommée foo (int first_param, int second_param, int third_param) dans la 'convention d'appel C', les codes d'asm ressembleront à cela :

push [3e_paramètre] ; Pousse le 3e paramètre sur la pile
push [2e_paramètre] ; Suivi par le second
push [1er_paramètre] ; Et du 1er
call Procédure_foo
add sp, 12 ; La fonction appelante équilibre le remplissage de la pile
la convention d'appel PASCAL est le contraire de la convention C. elle passe les paramètres de la pile de gauche à droite et le Call est responsable de équilibre (de l'état) de la pile après l'appel.
Win16 adopte la convention PASCAL parce qu'il produit des codes plus petits. La convention C, est utile quand vous ne savez pas combien de paramètres on passera à la fonction comme dans le cas de wsprintf (). Dans le cas de wsprintf (), la fonction n'a aucune façon de déterminer à l'avance combien de paramètres seront préservés sur la pile, donc on ne peut pas déterminer l'état de la pile.
STDCALL est l'hybride de la Convention de Pascal et C. Il passe les paramètres de droit à gauche. La plate-forme Win32 emploie exclusivement STDCALL . Sauf dans un cas : wsprintf (). Vous devez employer la convention C avec wsprintf ()

.DATA
.DATA?
.CONST
.CODE
Ces quatre directives servent a appeler la section (00401000……). Vous n'avez pas de segments (0001.xxxx, 0002.xxxx,…) dans Win32, vous vous souvenez ? Mais vous pouvez diviser votre espace d'adresse (00401000 à 00620000 par exemple) en sections logiques, exemple : " le code de 00401000 à 005F0000 " et "les DATA de 005F0000 à 00620000 ". Le début d'une section dénote la fin de la section précédente. Il existe deux groupes de section : données et code. Les sections de données sont divisées en 3 catégories :

  • .DATA Cette section contient les données initialisées de votre programme.
  • .DATA? Cette section contient les données non initialisées de votre programme. Parfois vous voulez juste prévoir un espace mémoire pour contenir de futurs données, mais ne voulez pas l'initialiser (lui donner une valeur initiale). c'est le but de cette section. L'avantage des données non initialisées est : qu'il ne prend pas d'espace dans le fichier exécutable. Par exemple, si vous allouez(prévoyez) 10,000 octets dans votre .DATA ? La section de votre exécutable ne sera pas gonflée de 10,000 octets. Sa taille reste pratiquement la même. Vous dites seulement à l'assembleur de combien de place votre programme aura besoin une fois qu'il sera chargé en mémoire, c'est tout.
  • .CONST Cette section contient la déclaration des constantes employées par votre programme. Les constantes qui sont dans cette section ne seront jamais modifiées par votre programme. elles resteront seulement des *constantes*.

Vous n'êtes pas forcément obligé d'employer ces trois sections dans votre programme. Déclarez seulement la (ou les) section (s) que vous voulez utiliser.

Il n'y a seulement qu'une section pour le code : .CODE. C'est celle où vos lignes de code résident.
<label>
end <label>
Où <label> représente n'importe quelle étiquette arbitraire, et est employé pour indiquer le début et la fin de votre code. Les deux 'label' doivent être identiques. Toutes vos lignes de code doivent se trouver entre <label> et end <label>

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 ASM tutorial 2: MessageBox

Tutorial 2: MessageBox

Dans ce tutorial, nous créerons un programme Windows entièrement fonctionnel qui montre une MessageBox (boîte de message) disant "Win32 assembly is great!".

Téléchargez le fichier d'exemple ici.

Théorie:

Windows met à notre disposition de multiples ressources pour les programmes Win (programmes fonctionnant sous windows). La plus importante c'est l'API (Applications Programmant l'Interface). Les API représentent une collection énorme de fonctions très utiles qui résident dans Windows lui-même, prêtes pour l'utilisation de n'importe quels autres programmes Win (programmes tournant sous Windows contrairement à ceux sous Dos). Ces fonctions sont stockées dans plusieurs bibliothèques dynamiquement liées (les DLLs) comme kernel32.dll, user32.dll et gdi32.dll. Kernel32.dll Contient les fonctions API qui traitent avec la mémoire et sa gestion. User32.dll contrôle les interfaces dont se sert votre programme. Gdi32.dll est responsable d'opérations graphiques. "Les trois principales " sont là, d'autre DLLs peuvent être employées pour votre programme, pourvu que vous ayez assez d'informations sur les fonctions API désirées.
Les programmes Win se lient dynamiquement avec ces DLLs, c'est-à-dire : Les codes des fonctions API ne sont pas inclus dans le programme Win exécutable. Dans votre programme, pendant son exécution, pour accéder à l'API désirée, vous devez déclarer cette information dans le fichier exécutable. L'information est dans une bibliothèque d'importation. Vous devez lier vos programmes avec les bibliothèques d'importation correctes ou ils ne seront pas capables de retrouver la fonction de l'API souhaitée.
Quand un programme Win est chargé dans la mémoire, Windows lit l'information stockée dans le programme. Cette information inclut les noms des fonctions et les DLLs où résident ces fonctions. Ex : la DLL User32.dll contient la fonction MessageBox (ainsi qu'une multitude d'autres fonctions). L'information que cherchera Windows dans votre exécutable ce sera quelque chose comme " Call USER32 ! MessageBox ". Dès que Windows trombera sur un tel renseignement dans votre programme, il chargera la DLL et exécutera la fonction.
Il y a deux catégories de fonctions API : les uns pour ANSI et les autres pour Unicode. Les noms des fonctions API pour ANSI sont suivis du suffixe "A", ex : MessageBoxA. Ceux pour Unicode ont le suffixe "W". Windows 95 traite naturellement ANSI, et Windows NT Unicode.
D'habitude on se sert de la convention ANSI, qui pour l'ensemble des caractères proposés par votre ordinateur est terminés par le caractère NULL. ANSI représente chaque caractère sur 1 octet. Il est suffisant pour les langues européennes, mais il ne peut pas manipuler la plupart des langues orientales qui ont plusieurs milliers de caractères uniques. C'est pourquoi UNICODE entre en jeu. Un caractère UNICODE a une taille de 2 octets, ce qui lui permet d'avoir une série de code de 65536 caractères uniques.
Mais la plupart du temps, vous emploierez un fichier INCLUDE (*.inc) qui peut déterminer et choisir les fonctions API appropriées à votre plate-forme suivant ce que doit faire votre programme. Référez-vous juste aux noms de fonction API sans le suffixe.

Example:

Je vous ai présenté ci-dessous le squelette nu d'un programme. Nous l'enrichirons plus tard.

.386
.model flat, stdcall
.data
.code
start:
end start

L'exécution commence à la première instruction immédiatement au-dessous de l'étiquette Start: Le programme s'exécutera instruction par l'instruction jusqu'à ce qu'une instruction de contrôle de direction comme jmp, jne, je ,ret Etc..soit trouvée. Ces instructions dirigent l'exécution vers d'autres lignes d'instructions. Quand le programme a besoin de sortir de Windows pour se terminer, il doit appeler la fonction ExitProcess qui est une API de la DLL Kernel32.dll.

ExitProcess proto uExitCode:DWORD

Ci dessus, ce type d'expression est appelé un prototype de fonction. Un prototype de fonction définit les attributs d'une fonction pour ensuite transformer cette expression (pseudo-assembler) en véritable assembler. Le format d'un prototype de fonction est le suivant :

FunctionName PROTO [ParameterName]:DataType,[ParameterName]:DataType,...

En résumé, le mot-clé PROTO est précédé du nom de la fonction puis suivi de la liste des paramètres, séparés par des virgules. Dans l'exemple ExitProcess ci-dessus, il définit ExitProcess comme une fonction qui prend seulement un seul paramètre de type DWORD. Les prototypes de fonctions sont très utiles quand vous employez la syntaxe d'appel de niveau haut :INVOKE. En plus, le Linker vérifiera si vous avez correctement utilisé la fonction que vous invokez. En outre, il vérifiie que ses paramètres ne sont pas oubliés.
Par exemple, si vous faites :

call ExitProcess

Sans pousser un dword sur la pile (Push 0, avant d'appeler la fonction ExitProcess), l'assembler ne sera pas capable de comprendre cette erreur à votre place. Vous vous en apecevrez plus tard quand votre programme plantera. Mais si vous employez :

invoke ExitProcess

Le linker vous informera que vous avez oublié de pousser un dword sur la pile évitant ainsi l'erreur. Je vous recommande donc d'employer. invoke au lieu d'un simple 'Call'. La syntaxe d''Invoke' est la suivante :

INVOKE expression [,arguments]

L'expression peut être le nom d'une fonction ou bien un pointeur de fonction. Les paramètres sont séparés par des virgules.

La plupart des prototypes de fonction pour des fonctions API sont tenus dans les fichiers nommés : INCLUDE. Si vous employez MASM32, ils seront dans le dossier MASM32/INCLUDE. Les fichiers INCLUDE ont l'extension .inc et les prototypes de fonction pour une DLL sont stockés dans le fichier .inc avec le même nom que la DLL. Par exemple, ExitProcess est exporté par kernel32.lib donc le prototype de fonction pour ExitProcess est stocké dans kernel32.inc.
Vous pouvez aussi créer des prototypes de fonction pour vos propres fonctions. (HéHé ! ! !)
Partout dans mes exemples, j'emploierai windows.inc que vous pouvez télécharger à http://win32asm.cjb.net

Maintenant revenons à ExitProcess, uExitCode est la valeur du paramètre que vous voulez que votre programme renvoie à Windows après que celui-ci se termine. Vous pouvez appeler ExitProcess comme cela :

invoke ExitProcess, 0

Si vous placez cette ligne immédiatement après l' étiquette de commencement (ici on l'a appelée arbitrairement 'Start :', vous obtiendrez un programme win32 qui sortira immédiatement de Windows (pour revenir à ce que vous aviez à l'écran juste avant d'exécuter votre programme), mais c'est néanmoins un programme valable.

.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:
invoke ExitProcess,0
end start

Pour en savoir plus sur ExitProcess, lisez ExitProcess.html

L'option casemap:none indique à MASM de ne pas faire de différence entre les expressions ExitProcess et exiteprocess par exemple. Notez la nouvelle directive, include. Cette directive est suivie par le nom du fichier que vous voulez insérer à sa place. Dans cet exemple, quand MASM traite la ligne include \masm32\include\windows.inc, Il ouvrira windows.inc qui est dans le dossier \MASM32\include et fera en sorte que son contenu (celui de windows.inc) soit collé dans votre programme win32. Il ne contient pas de prototype de fonction. windows.inc n'est en aucun cas complet(c'est compréhensif car on peut toujours y rajouter de nouvelles choses). Ce fichier 'windows.inc' qui regroupe tous mes fichiers.inc je l'appelle le fichier 'HUTCH'. Il sera constamment remis à jour. Vérifiez donc HUTCH de temps en temps sur ma page d'accueil pour des mises à jour.

Dans notre exemple ci-dessus, nous appelons une fonction exportée par kernel32.dll, donc nous avons besoin d'inclure le prototype de fonction de kernel32.dll. Ce fichier est kernel32.inc. Si vous l'ouvrez avec un éditeur de texte, vous verrez que c'est plein de prototypes de fonction pour kernel32.dll. Si vous n'incluez pas kernel32.inc, vous pouvez toujours appeler ExitProcess, mais seulement avec la syntaxe d'appel simple (Push 0 puis Call ExitProcess). Vous ne serez pas capables d'invoker la function (ExitProcess).

*** Faisons le point : pour invoquer une fonction, vous devez mettre son prototype de fonction (qui est le contenu d'un des fichier *.inc) quelque part dans le code source. Dans notre exemple, si vous n'incluez pas kernel32.inc, vous pouvez définir le prototype de fonction pour ExitProcess n'importe où dans le code source au-dessus de la commande invokée et ça marchera. Les fichiers invokés doivent ici vous faciliter le travail en vous épargnant de taper les prototypes vous-même, donc employez-les chaque fois que vous le pouvez.
Maintenant nous rencontrons une nouvelle directive, includelib. includelib ne fonctionne pas comme include. C'est seulement une façon de dire à l'assembleur quelles bibliothèques d'importation sont employées par vos programmes. Quand l'assembleur voit une directive includelib, il met une commande de linker dans le fichier d'objet pour que le linker sache avec quelles bibliothèques d'importation votre programme a besoin de se lier. Vous n'êtes pas forcé d'employer includelib quoique. Vous pouvez spécifier les noms des bibliothèques d'importation dans la ligne de commande du linker, mais croyez-moi, c'est ennuyeux et la ligne de commande ne peut contenir que 128 caractères.

Sauvegardez maintenant cet exemple sous le nom de msgbox.asm. Vérifiez le chemin d'accès du fichier ml.exe, et assemblez msgbox.asm avec:

    ml /c /coff /Cp msgbox.asm
  • /c dit à MASM de seulement assembler. ça n'appelle pas link.exe. La plupart du temps, on ne veut pas appeler link.exe automatiquement puisque on doit exécuter quelques autres tâches avant l'appel de link.exe.

    • /coff dit à MASM de créer le fichier .obj dans le format COFF. MASM emploie une variante du format COFF (le Format de Fichier d'Objet Commun) qui est employé sous Unix comme son propre objet et le format de fichier exécutable.
    /Cp Dit à MASM de préserver les identificateurs d'utilisateur. Si vous employez les fichier du HUTCH (Les fichiers .inc), vous pouvez mettre l'"option casemap:none" en entête de votre source code. La directive .model réalise le même effet.
Après que vous ayez assemblé avec succès msgbox.asm, vous obtiendrez msgbox.obj. msgbox.obj est un fichier objet. Un fichier objet n'est pas loin d'un fichier exécutable. Il contient les instructions et les données dans la forme binaire. Ce qui manque sont chacunes des adresses fixes devant les instructions. Et ça, c'est le linker qui va faire le boulot.

Alors allons y avec link:

    link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm32\lib msgbox.obj
/SUBSYSTEM:WINDOWS Ça informe 'Link' de quel sorte d'exécutable est votre programme.
/LIBPATH:<path to import library> Dit à Link où sont les bibliothèques d'importation. Si vous utilisez MASM32, ils seront dans le dossier MASM32\LIB.
Link lit dans le fichier *.obj et pose les adresses des bibliothèques d'importation. Quand le processus est fini vous obtenez msgbox.exe.

Maintenant vous arrivez à msgbox.exe. Continuez, exécutez-le. Vous constaterez qu'il ne fait rien. Bien, nous n'y avons rien mis d'intéressant encore. Mais c'est un programme de Windows néanmoins. Et regardez sa taille! Dans mon PC, il prend 1,536 octets.

Maintenant, nous allons créer une boîte de message. Son prototype de fonction est :

MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD

hwnd est le 'handle' ou la poignée de la fenêtre parente. Vous pouvez vous imaginer qu'un 'Handle' est un numéro qui représente la fenêtre à laquelle vous faîtes référence. Sa valeur n'a pas d'importante pour vous. Vous vous rappelez seulement qu'il représente la fenêtre. Quand vous voulez faire quelque chose avec la fenêtre, vous devez vous y référer par son 'handle'.
lpText est un pointeur sur le texte que vous voulez montrer en tant que Contenu de la MessageBox (boîte de message). Cet pointeur représente réellement l'adresse de quelque chose. Cette adresse sera quelque chose du genre 00602154 et elle sinifie que le contenu qui sert à votre boite de message est en fait écrit à partir de la mémoire 00602154 (Cette adresse fait toujours parti des DATA) = Le Contenu De La MessageBox
lpCaption est l'pointeur de texte qui sert cette fois à accèder à l'adresse du texte qui est le titre de la message box. = Le Titre De La MessageBox
uType spécifie le type de message box utilisée. Par exemple quels sont les boutons quelle doit avoir, est-ce une MessageBox simple ou bien une avec un signe particulier comme le Point d'Interrogation...
Pour plus d'informations concernant les flags de MessageBox, regardez le fichier suivant : msgbox.html

Modifions msgbox.asm pour inclure une MessageBox.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib

.data
MsgBoxCaption db "Iczelion Tutorial No.2",0
MsgBoxText db "Win32 Assembly is Great!",0

.code
start:
invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start

Assemblez le fichier msgbox.asm et exécutez le nouveau prog msgbox.exe. Vous verrez une boîte de message montrant le texte"Win32 Assembly is Great!".

Regardons de nouveau le code source.
Nous avons définis deux données terminées par un zéro dans la section .data. Rappelez-vous que chaque données ANSI dans Windows doit être terminée par le caractère NULL (0 hexadécimal).
Nous avons aussi employé deux constantes, le NULL et MB_OK. Ces constantes sont définies dans windows.inc. Donc vous pouvez faire référence à leurs noms à la place de leurs valeurs. Cela améliore la lisibilité De votre code source.
L'expression ADDR est employée pour passer l'adresse d'une données ou d'un label à la fonction. C'est valable seulement dans le contexte de la directive 'invoke'. Vous ne pouvez pas l'employer pour assigner l'adresse d'une donnée à un registre / variable, par exemple. Vous devez employer offset au lieu de 'ADDR' dans ce cas. Cependant, il y a quelques différences entre les deux :

  1. addr ne peut pas manipuler à l'avance la référence tandis qu' offset le peut. Par exemple, si la ligne où est rendu le programme, invoke un label qui est défini quelque part plus loin dans le code source, ADDR ne marchera pas.
    2. invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
    ......
    MsgBoxCaption db "Iczelion Tutorial No.2",0
    MsgBoxText db "Win32 Assembly is Great!",0
    MASM indiquera une erreur. Si vous utilisez offset au lieu de addr dans ce petit bout de code, alors MASM l'assemblera avec succès.
  2. addr peut manipuler des variables locales tandis qu' offset ne le peut pas. . Une variable locale est seulement un petit espace réservé sur la pile. Vous ne connaîtrez seulement son adresse que le temps de l'exécution. offset est interprété pendant le déroulement du programme par l'assembleur. Donc il est naturel qu' offset ne travaille pas avec des variables locales. addr est capable de manipuler des variables locales grâce au fait que l'assembleur vérifie d'abord si la variable mentionnée par addr est globale ou locale. Si c'est une variable globale, il met l'adresse de cette variable dans le fichier d'objet. À cet égard, il travaille comme offset. Si c'est une variable locale, il produit un ordre d'instruction qu'il appelle en réalité avant la fonction :
    3. lea eax, LocalVar
    push eax

    Puisque 'lea' peut déterminer l'adresse d'un label pendant l'exécution, ça fonctionne tès bien.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 ASM tutorial 3: Une Simple Fenêtre

Tutorial 3: Une Simple Fenêtre

Dans ce tutorial, nous construirons un programme Win qui ouvre une fenêtre entièrement fonctionnelle sur le bureau. (Un programme Win, est un programme qui tourne sous Windows et non pas comme certains sous DOS).
Downloadez le fichier en exemple ici

Théorie:

Les programmes Win font fréquemment appels aux fonctions API pour leur GUI (NdT : Graphic User Interface). Le GUI c'est l'ensemble des objets tels qu'une boîte de saisie de texte, les boutons... En fait, pour fabriquer un programme Win32 on crée d'abord une fenêtre principale vide(c'est ce qu'on va faire dans ce tutorial) puis on place par dessus des objets du GUI suivant ce qu'on veut. Il faut bien un lien entre la fenêtre principale et ses objets, car il peut aussi y avoir plusieurs fenêtres principales d'ouvertes, ce lien c'est le n°'Handle' de la fenêtre principale qui est donnée comme paramètre à chaque objet pour dire que ces objets font parti de cette fenêtre-ci et non d'une autre. Ceci est à la fois bénéfique pour les utilisateurs et pour les programmeurs. Ainsi les utilisateurs n'ont plus besoin de comprendre comment le GUI de chaques nouveaux programmes fonctionne, le GUI de chaque programme Win est semblable. Pour les programmeurs, les codes du GUI sont déjà à sa disposition, évalués et prêts à être utilisés. Le revers pour les programmeurs c'est la complexité accrue. Pour créer ou manipuler n'importe quels objets du GUI comme des fenêtres, un menu ou des icônes, les programmeurs doivent suivre une recette stricte. Mais ceci peut être surmonté par la programmation modulaire ou le paradigme OOP.
Ci-dessous, je vous montre les étapes exigées pour créer une fenêtre sur le bureau:
  1. Déclarez l'"instance handle" de votre programme (obligatoire)

    2. l'instance handle du prog est l'adresse mémoire de base où windows à chargé l'exe dans le processeur, en général 400000 pour un exe et 100000 pour une DLL. Cette valeur sert à des fonctions win32 pour charger des ressources.
  2. Déclarez la 'command line' (Non exigé à moins que votre programme ne veuille traiter une ligne de commande)

    3. 'GetCommandLine' sert à connaître la ligne de commande lors du lancement de l'appli, des paramètres. exemple -->monprog.exe cmd1,cmd2 ,et tout ce qui est après exe fait partie de la ligne de commande.
  3. Définissez la 'window class' (Obligatoire, à moins que vous utilisiez un type de fenêtre prédéfini telle qu': une MessageBox ou une DialogBox)
  4. Créez la fenêtre (ben oui Obligé)
  5. Affichez la fenêtre sur le bureau (Obligé à moins que vous ne vouliez pas montrer la fenêtre immédiatement)
  6. Rafraichir l'intérieur de la (Client Area) zone de travail de notre fenêtre
  7. Créer un bout de code qui tourne sur lui-même à l'infini, capable de vérifier les messages de Windows (comme l'attente d'une action)
  8. Si des messages sont reçus, ils sont traités par une fonction spécifique
  9. Sortie du programme si l'utilisateur referme la fenêtre
Comme vous pouvez le voir, la structure d'un programme Win est plutôt complexe comparée à un programme fonctionnant sous DOS. Mais le monde de Windows diffère résolument du monde de DOS. Les programmes Win doivent être capables de coexister paisiblement les uns avec les autres. Ils doivent suivre des règles très strictes. Chaque programmeur doit aussi être très stricte avec son style de programmation et d'habitudes.

Contenu:

Ci-dessous voici le code source de notre programme ouvrant une simple fenêtre. Avant une plongée dans les entrailles de la programmation Win32 ASM, regardons quelques points importants qui soulageront votre programmation.
  • Vous devez mettre tous les 'Constants Windows', les structures et les prototypes de fonction dans un fichier INCLUDE (*.inc) et l'inclure au début de votre fichier .asm. Cela vous évitera de gros efforts de réécriture par vous même. Actuellement, la plupart des fichiers INCLUDE ,tel que windows.inc, sont complets. La totalité des fichiers INCLUDE forment le répertoire HUTCH que vous pouvez télécharger sur ma page. Vous pouvez aussi définir vos propres constants et structures mais vous devez les mettre dans un fichier INCLUDE (*.inc) différent de 'windows.inc'.
    Les 'constants Windows', les 'structures' et les 'Prototypes de Fonction' sont toutes des instructions assembleur. Donc si vous n'avez pas compris, on écrit seulement des bouts de programme en ASM dans des fichiers *.inc et quand vous en avez besoin pour un de vos programmes, vos y faites référence grâce à une instruction spéciale :'INCLUDE…'
  • Utilisez la directive includelib pour indiquer quelle bibliothèque d'importation est employée par votre programme. Par exemple, si votre programme appelle la fonction MessageBox, vous devez mettre la ligne :
      • includelib user32.lib
    au début de votre fichier .asm. Cette directive informe MASM que votre programme utilisera certaines fonctions dans cette bibliothèque d'importation. Si vos fonctions d'appels de programme ne font pas parti de la même bibliothèque, ajoutez juste un IncludeLib pour chaque bibliothèque que vous employez. En employant la directive IncludeLib, vous n'avez plus à vous inquiétez des bibliothèques d'importations. Vous pouvez employer /LIBPATH linker comme commutateur pour dire où se trouvent tous les libs et y faire ainsi référence.
  • En déclarant des prototypes de fonction API, des structures, ou des constants pour les inclure dans vos propres fichiers, essayez de respecter les noms d'origines employés par les fichiers INCLUDE de Windows. Ça vous évitera bien des maux de tête à chercher quelque article dans la référence Win32 API.
  • Employez le fichier makefile.bat pour automatiser votre processus d'assemblage. Cela vous évitera encore de tapez pleins de lignes de code à chaque fois.
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib ; Fait appel à des fonctions dans user32.lib et kernel32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA ; Initialise les DATA (ceux qui font déjà parti du programme)
ClassName db "SimpleWinClass",0 ; le nom de Class de votre fenêtre
AppName db "Our First Window",0 ; Le nom de votre fenêtre (c'est son titre)

.DATA? ; Initialise les DATA (ceux qui ne font pas encores parti du programme. EX: les futures saisies au clavier).
hInstance HINSTANCE ? ; l'Instance handle de votre programme
CommandLine LPSTR ?
.CODE ; C'est ici que commence votre code.
start:
invoke GetModuleHandle, NULL ; Récupère l'Instance handle de votre programme.
; Sous Win32, hmodule==hinstance mov hInstance,eax
mov hInstance,eax
invoke GetCommandLine ; Obtient la command line. vous n'avez besoin d'appeler cette fonction QUE SI
; votre programme n'a pas encore de command line.
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT ; Appelle la fonction principale
invoke ExitProcess, eax ; Sort de votre programme. Le code de sortie est retourné dans eax.

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX ; Créez des variables locales sur la pile
LOCAL msg:MSG
LOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEX ; Place les valeurs dans les membres de wc
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc ; Enregistre notre 'Window Class', ainsi elle est définit
invoke CreateWindowEx,NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow ; Affiche notre fenêtre sur le bureau
invoke UpdateWindow, hwnd ; Régénérez le client area

.WHILE TRUE ; Création d'une boucle pour traiter les messages qui arrivent.
invoke GetMessage, ADDR msg,NULL,0,0 (comme détection du déplacement de la souris)
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam ; Retoune le code de sortie dans eax
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY ; si l'utilisateur ferme notre fenêtre (parce qu'on vient de recevoir un message WM_DESTROY)...alors
invoke PostQuitMessage,NULL ; on quitte notre application
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; Traitement par défaut de tous les autres messages qui ne nous intéressent pas.
ret
.ENDIF
xor eax,eax
ret
WndProc endp

end start

Analyse:

Vous pouvez être déconcertés par le fait qu'un programme ouvrant une simple fenêtre exige tant de codes. Mais la plupart de ces codes sont juste *une ossature commune pour tous les prog*. Ces codes vous pouvez les copier d'un fichier de code source à un autre. Ou si vous préférez, vous pourriez assembler quelques-uns de ces codes dans une bibliothèque pour être employés comme une sorte d''introduction' dans votre programme. Ensuite vous pourrez vous contenter d'écrire les lignes de codes dans la fonction WinMain. Où 'WinMain' est en fait un peu comme la procédure principale dans laquelle vous écrirez votre programme. Sachez que c'est ce que font les compilateurs C. Ils vous laissent écrire uniquement les codes de WinMain (ou dans WinMain) sans s'inquiéter du reste. Le seul inconvénient c'est que vous devez avoir une fonction nommée WinMain sinon les compilateurs C ne seront pas capables de combiner vos codes avec ceux de votre INTRODUCTION. Vous n'aurez pas de telles restrictions avec le langage Assembleur. Vous pouvez employer d'autres noms de fonction que 'WinMain' ou bien même aucune fonction du tout
Soyez prévenus. Ça va être un long tutorial. On va analyser ce programme à mort!.
    .386
    .model flat,stdcall
    option casemap:none

    WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

    include \masm32\include\windows.inc
    include \masm32\include\user32.inc
    include \masm32\include\kernel32.inc
    includelib \masm32\lib\user32.lib
    includelib \masm32\lib\kernel32.lib

Les trois premières lignes sont "NECESSAIRES". .386 dit à MASM que nous avons l'intention d'employer le jeu d'instruction de processeur 80386 dans ce programme. .model flat,stdcall dit à MASM que notre programme emploie la mémoire plate adressant le modèle. C'est pourquoi nous employions le paramètre stdcall prenant par défaut cette convention dans notre programme.
Ensuite c'est au tour du prototype de fonction de WinMain. Puisque nous appellerons WinMain plus tard, nous devons définir son prototype de fonction d'abord, pour que nous puissions l'invoquer par la suite.
Nous devons inclure windows.inc au début du code source. Il contient les structures importantes et les constants qui sont employés par notre programme. Le fichier INCLUDE, windows.inc, est simplement un fichier texte. Vous pouvez l'ouvrir avec n'importe quel éditeur de texte. Notez s'il vous plaît que windows.inc ne contient pas toutes les structures et constants (encore). Pour ce qui est du HUTCH (le groupe de fichiers *.inc) j'y travaille.
Notre programme appelle les fonctions API qui résident dans user32.dll (CreateWindowEx, RegisterWindowClassEx, par exemple) et kernel32.dll (ExitProcess), donc nous devons lier notre programme avec ces deux bibliothèques d'importation. La question suivante : comment puis-je savoir qui importe la bibliothèque qui doit être liée avec mon programme ? La réponse : Vous devez savoir où résident les fonctions API appelées par votre programme. Par exemple, si vous appelez une fonction API dans gdi32.dll, vous devez vous lier avec gdi32.lib.
C'est l'approche spécifique de MASM. Pour TASM, la bibliothèque d'importation qui est liée à votre programme est uniquement : import32.lib. C'est beaucoup , beaucoup plus simple.
    .DATA
    ClassName db "SimpleWinClass",0
    AppName db "Our First Window",0

    .DATA?
    hInstance HINSTANCE ?
    CommandLine LPSTR ?

Ci-dessus on trouve les 2 sections pour les "DATA".
Dans .DATA, on déclare deux données terminées chacune par zéro : ClassName est le nom de notre classe de fenêtre (de notre Window Class).
'AppName' est le nom de notre fenêtre. Notez que les deux variables sont initialisées.
Dans .DATA?, Deux variables sont déclarées : HINSTANCE (l'Instance Handle de notre programme) et CommandLine (ligne de commande de notre programme). Ces deux données sont extrêmement courrantes, HINSTANCE et LPSTR, sont vraiment de nouveaux noms (de type: DWORD). Vous pouvez les voir dans windows.inc. Notez que chacunes des variables se trouvant dans la section .DATA ? ne sont pas initialisées, c'est-à-dire qu'elles ne doivent pas contenir de valeur spécifique en démarrage, mais nous réservons de l'espace pour notre future utilisation.
    .CODE
    start:
    invoke GetModuleHandle, NULL
    mov hInstance,eax
    invoke GetCommandLine
    mov CommandLine,eax
    invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
    invoke ExitProcess,eax
    .....
    end start
.CODE contient toutes vos instructions ASM. Vos codes doivent résider entre le label " Start : " et le label " End Start ". Le nom du label est arbitraire. Vous pouvez le nommer comme ça vous chante tant qu'il est unique et ne viole pas la convention de nom de MASM. Par exemple vous ne pouvez pas prendre le mot 'Mov' comme nom de label.
Notre première instruction est l'appel GetModuleHandle pour aller chercher l'Instance handle de notre programme. Sous Win32, l' Instance Handle et Module Handle sont les mêmes. Vous pouvez vous représenter l'Instance Handle comme le n° d'identification de votre programme. Il est employé comme paramètre par plusieurs des fonctions API que notre programme doit appeler, donc c'est généralement une bonne IDée de le déclarer dès le début de notre programme.
Notez : En réalité sous win32, l'Instance Handle est l'adresse linéaire de votre programme dans la mémoire.
Après avoir fait appel à une fonction de Win32 (une API), la valeur de retour de cette fonction, est placée dans eax. Toutes les autres valeurs sont renvoyées par des variables que vous avez vous-même défini avant le Call qui appel cette fonction.
Une fonction Win32 (ou une procédure) que vous appelez(Call) souvent préservera les Registres de segment ebx, edi, esi et le Registre ebp. Au contraire, ecx et edx sont considérés comme des Registres de travail et sont toujours indéfinis après la sortie d'une fonction de Win32 (sortie d'une procédure).
Notez : n'espérez pas que les valeurs d'eax, ecx, edx soient préservés après un appel de fonction API.
La ligne qui suit un appel (un call) à une fonction d'API, attend en retour une valeur dans eax. Dès que vous appelez une fonction API de Windows, vous devez utiliser la règle suivante : préservez puis rétablissez les valeurs des Registres de segment ebx, edi, esi et ebp après le retour de la fonction sinon votre programme plantera peu de temps après, ceci est valable pour vos procédures Windows et pour les fonctions de rappel de service Windows.
L'appel 'GetCommandLine'est inutile si votre programme ne traite pas de ligne de commande. Dans cet exemple, je vous montre comment l'appeler dans le cas où vous en auriez besoin dans votre programme.
Vient ensuite l'appel de WinMain. Ici il reçoit quatre paramètres : l'Instance Handle de notre programme, l'Instance Handle du précédent Instance de notre programme (un programme de plus haut niveau qui aurait appeler le notre), la Command Line et l'état de la fenêtre à sa première apparition. Sous Win32, il n'y a AUCUN Instance précédent(premier). Chaque programme est seul dans son espace d'adresse, donc la valeur d'hPrevInst est toujours 0. C'est un des restes de Win16 quand tous les programmes étaient encore exécutés dans un même espace d'adresses pour savoir si c'était le premier Instance ou non. Donc sous win16, si hPrevInst est NUL, alors c'est que c'est le premier Instance.
Notez : Vous n'êtes pas obligé de déclarer le nom de fonction en tant que : 'WinMain'. En fait, vous avez une totale liberté à cet égard. Et même, vous pouvez ne pas employer de fonction WinMain-équivalent du tout. Le but est de placer votre code (votre véritable programme) à l'intérieur de la fonction WinMain à côté de GetCommandLine et votre programme sera toujours capable de fonctionner parfaitement.
A la sortie de WinMain, eax est rempli du code de sortie. Nous passons ce code de sortie comme paramètre à ExitProcess qui sert à terminer notre programme.

WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

La susdite ligne est la déclaration de la fonction WinMain. Notez l'ensemble des paramètres qui suivent la directive PROC. Ce sont les paramètres que WinMain reçoit de la fonction qui l'appelle. Vous pouvez utiliser les noms de ces paramètres au lieu de vous servir de la pile pour pousser la valeurs de vos paramètres.

LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

La directive LOCAL alloue de la mémoire sur la pile pour des variables locales employées par la fonction. La totalité des directives LOCALES doivent être placées juste au-dessous de la directive PROC. La directive LOCAL est immédiatement suivie par " le nom de la variable locale " ; " le type de variable ". Et donc 'LOCAL wc:WNDCLASSEX' dit à MASM de réserver de la mémoire sur la pile. Nous pourrons ensuite nous référer à wc dans notre code sans aucune difficulté engendrée par la manipulation de la pile. C'est vraiment une aubaine, je pense. le revers est le fait que les variables locales ne peuvent pas être employées à l'extérieur de la fonction, elles sont créés et seront automatiquement détruites quand la fonction retourne à l'appel. Un autre inconvénient est que vous ne pouvez pas initialiser des variables locales automatiquement parce qu'elles sont uniquement allouées dans la mémoire de la pile au moment seulement où la fonction est entrée. Vous devez les assigner manuellement avec les valeurs désirées après les directives 'LOCAL'.

mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc

Les lignes ci-dessus sont vraiment simples par leur concept. Il s'agit juste de plusieurs lignes d'instruction regroupées. Le concept qui se cache derrière toutes ces lignes c'est d'initialiser la Windows class. Une 'Windows Class' n'est rien de plus que la première ébauche d'une fenêtre. On définit donc plusieurs caractéristiques importantes d'une fenêtre comme son icône, son curseur, la fonction qui la contrôle, sa couleur etc. Vous définisez la fenêtre grâce à sa 'Windows Class'. C'est en quelque sorte le but de ce concept. Si vous voulez créer plusieurs fenêtres avec les mêmes caractéristiques, il serait temps de penser à stocker toutes ces caractéristiques dans un unique endroit (donc dans une procédure par exemple) et de s'y référer quand c'est nécessaire. Cet arrangement économisera pas mal de mémoire en évitant la duplication d'informations. Windows doit être très efficaces dans l'utilisation des ressources mémoires, qui restent encore rares.
Faisons le point : Dès que vous définissez votre propre fenêtre, vous devez définir ses caractéristiques grâce à l'instruction WNDCLASS ou WNDCLASSEX et ensuite appeler RegisterClass ou RegisterClassEx avant que vous ne soyez capables de créer votre fenêtre. Vous devrez donner une 'Windows Class' pour chaque type de fenêtre que vous voulez créer.
Windows possède plusieurs 'Windows Class' déjà prédéterminées, comme les boutons et la boîte d'édition (pour entrer un texte). Pour ces fenêtres (ou ces Controls), vous n'avez pas besoin de déclarer une 'Windows Class', appelez juste CreateWindowEx avec le nom de classe prédéterminée.
L'élément le plus important de WNDCLASSEX est lpfnWndProc. Lpfn attend un 'Pointeur Long' pour fonctionner. Sous Win32, il n'y a pas de Pointeur(proche) ou de Pointeur(lointain), mais un simple Pointeur grâce au nouveau modèle de mémoire Flat(PLAT). Mais c'est encore un reste des vieux jours de Win16. Chaque 'Windows Class' doit être associée à une procédure appelée par Windows. La procédure qui sert à créer vos fenêtres sera responsable du traitement de chaque message associée la à Windows Class. Les fenêtres enverront des messages vers la procédure qui les gère pour lui notifier quels événements importants sont utilisés par ces fenêtres, comme l'utilisation du clavier ou de la souris. Il ne reste plus à la procédure (qui s'occupe de la gestion des fenêtres) qu'à répondre intelligemment à chaque message qu'elle reçoit (Ex le fait de bouger la souris envoie un message dans cette procédure pour qu'on puisse en faire qque chose d'intéressant etc..). La plupart du temps vous écrirez des enchaînements d'événements dans cette procédure.
Ci-dessous, Je vous détaille la construction de la fonction WNDCLASSEX:

WNDCLASSEX STRUCT DWORD
cbSize DWORD ?
style DWORD ?
lpfnWndProc DWORD ?
cbClsExtra DWORD ?
cbWndExtra DWORD ?
hInstance DWORD ?
hIcon DWORD ?
hCursor DWORD ?
hbrBackground DWORD ?
lpszMenuName DWORD ?
lpszClassName DWORD ?
hIconSm DWORD ?
WNDCLASSEX ENDS

cbSize: La taille de la structure WNDCLASSEX en octets. Nous pouvons employer l'opérateur SIZEOF pour obtenir (initialiser) cette valeur.
style: Le style (ou le type) de fenêtre créée pour cette 'Class'. Vous pouvez combiner plusieurs types ensembles en employant l'opérateur "or".
lpfnWndProc: L'adresse de la procédure responsable de la gestion des fenêtres créées dans cette 'Class'.
cbClsExtra: Indique le nombre d'octets supplémentaires à allouer en fonction de la structure(ses éléments) de la Windows Class choisie. Le système d'exploitation initialise les octets à zéro. Vous pouvez stocker des données spécifiques à la Windows Class ici.
cbWndExtra: Indique le nombre d'octets supplémentaires à allouer en fonction de la 'Windows Instance'. Le système d'exploitation initialise les octets à zéro.
hInstance: l'Instance Handle de ce module (de votre prog).
hIcon: Handle de l'icône. Obtenez-le après l'appel de 'LoadIcon' (ou en retour de l'appel de 'LoadIcon').
hCursor: Handle du curseur. Obtenez-le près l'appel de 'LoadCursor'.
hbrBackground: Couleur du fond de toutes les fenêtres créées dans cette 'Class'.
lpszMenuName: L'Handle du menu des fenêtres créées par cette 'Class'. C'est une valeur par défaut.
lpszClassName: Le nom de cette 'Class'.
hIconSm: L'Handle d'une petite icône associée à cette 'Class' de fenêtres. Si cette valeur est un NULL, le système recherche dans la 'ressource d'icône' indiquée par le membre hIcon, une icône de la taille appropriée sera employée en tant que petite icône.

invoke CreateWindowEx, NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL

Après l'enregistrement de la Window Class, nous pouvons appeler CreateWindowEx pour créer notre fenêtre basée sur la Window Class ainsi définie. Remarquez qu'il y a 12 paramètres à cette fonction, beuurr ! ! !

CreateWindowExA proto dwExStyle:DWORD,\
lpClassName:DWORD,\
lpWindowName:DWORD,\
dwStyle:DWORD,\
X:DWORD,\
Y:DWORD,\
nWidth:DWORD,\
nHeight:DWORD,\
hWndParent:DWORD ,\
hMenu:DWORD,\
hInstance:DWORD,\
lpParam:DWORD

On va voir la description détaillée de chaque paramètre :
dwExStyle: Styles de fenêtre supplémentaires. C'est le nouveau paramètre qui est ajouté à la vieille fonction 'CreateWindow'. Vous pouvez mettre des nouveaux styles de fenêtre pour Windows 95 & NT ici. En temps normal, vous pouvez spécifier votre style de fenêtre ordinaire dans dwStyle, mais si vous voulez certains styles spéciaux pour une fenêtre 'tip top', vous devez les spécifier ici. Vous devez employer un NULL(un 0) si vous ne voulez pas de styles de fenêtre supplémentaires.
lpClassName: (obligatoire).L'adresse de la suite de caractères ASCII contenant le nom de la Window Class que vous voulez employer en tant que gabarit pour cette fenêtre. La Class peut être votre propre Class que vous venez de créer ou bien une Window Class prédéterminée. Comme exposé ci-dessus, chaque fenêtre que vous avez créée doit être basée sur une Window Class. (Normal ! , puisque la Window Class sert justement à définir les propriétés de la fenêtre à créer)
lpWindowName: Adresse de la suite de caractères ASCII contenant le nom de la fenêtre. Sera en fait la titre de la fenêtre (en haut à gauche). Si ce paramètre est NULL, la barre de titre de la fenêtre sera vide.
dwStyle: C'est le style de la fenêtre. Vous pouvez spécifier ici l'apparition de la fenêtre. Mettez un NULL pour ce paramètre dwSTYLE est possible mais la fenêtre n'aurait aucun menu, n'aurait pas de boutons de réduction-agrandissement ni aucun autre bouton. La fenêtre n'aurait pas beaucoup d'intérêt. Vous devrez même appuyer sur Alt+F4 pour la refermer. Le style de fenêtre le plus commun est le paramètre 'WS_OVERLAPPEDWINDOW' (et non pas le paramètre NULL). Ainsi vous pouvez combiner plusieurs styles de fenêtre grâce à l'opérateur "OR" pour faire apparaître la fenêtre que vous désirez. Le style WS_OVERLAPPEDWINDOW est en réalité une combinaison des styles de fenêtres les plus communs.
X,Y: C'est la coordonnée du coin supérieur gauche de la fenêtre. Normalement on prend le paramètre CW_USEDEFAULT comme valeur, comme ça Windows décidera pour vous où placer la fenêtre sur le bureau.
nWidth, nHeight: Ce sont les largeur et hauteur de la fenêtre en pixels. Vous pouvez aussi employer CW_USEDEFAULT pour laisser Windows choisir la largeur appropriée et la hauteur à votre place.
hWndParent: C'est l'Handle de la fenêtre parente de votre fenêtre (si celle-ci existe). Ce paramètre indique à Windows si cette fenêtre est un enfant (Window Child) d'une autre fenêtre et, si elle l'est, qui est la fenêtre parente. Notez que ce n'est pas la même relation Parent-Enfant que les 'multiple document interface' ou (MDI). Les 'Window Child' n'ont pas forcément les mêmes caractéristiques que leur 'Window Parent'. Ces caractéristiques sont uniquement spécifiques à l'utilisation interne de chaque fenêtre. Si la fenêtre parente est détruite, toutes ses fenêtres enfant seront détruites automatiquement. C'est vraiment très simple. Dans notre exemple, il y a seulement une fenêtre, donc ce paramètre reste NULL.
hMenu: C'est l'Handle du menu de la fenêtre. Il est NULL si la Class Menu est utilisée. Revenez en arrière à un des membres de l'instruction WNDCLASSEX, soit 'lpszMenuName'. lpszMenuName indique le menu par *default* pour la 'Window Class'. Chaque fenêtre de cette Window Class aura le même menu par défaut. À moins que vous ne spécifiiez un menu *overriding* pour une fenêtre spécifique via son paramètre hMenu. hMenu est en réalité un paramètre à double usage. Si la fenêtre que vous souhaitez créer utilise un 'Type de Fenêtre' (Style) prédéterminé (c'est-à-dire un Control), un tel Control ne peut pas posséder de menu. hMenu est employé comme ID de ce Control au lieu de cela. Windows peut décider si hMenu est vraiment un Handle de menu ou un Control ID en regardant le paramètre lpClassName. Si c'est le nom d'une Window Class prédéterminée, hMenu est un Control ID. Si ce n'est pas, ce sera alors l' Handle du menu de votre fenêtre.
hInstance: l'Instance Handle pour le module (la partie) du programme créant la fenêtre.
lpParam: Le pointeur facultatif pour des données a passer à la fenêtre. Il est employé par la fenêtre MDI pour passer les données CLIENTCREATESTRUCT. Normalement, cette valeur est mise au NULL, signifiant que l'on ne passe aucunes données via CreateWindow (). La fenêtre peut prendre la valeur de ce paramètre après l'appel à la fonction GetWindowLong.

mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow
invoke UpdateWindow, hwnd

En retour couronné de succès de CreateWindowEx, l'Handle de la fenêtre est placée dans eax. Nous devons garder cette valeur pour une utilisation future. La fenêtre que nous venons de créer n'est pas automatiquement affichée. Vous devez appeler 'ShowWindow' avec l'Handle de fenêtre et la *résolution* désirée de la fenêtre pour la faire apparaître à l'écran. Ensuite vous pourrez appeler 'UpdateWindow' pour ordonner à votre fenêtre de repeindre son secteur de client (de réactualiser son contenu). Cette fonction est utile à chaque fois que vous voulez mettre à jour le contenu du secteur client. Vous pouvez omettre cet appel quoique.

.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW

Cette fois-ci, notre fenêtre est sur l'écran. Mais elle ne peut pas recevoir d'informations extérieur (comme une saisie de texte ou bien la détection de la pression d'un bouton). Donc nous devons l'informer de ce qui se passe. Nous faisons ça avec une boucle de message. Il y a seulement une boucle de message pour chaque module. Cette boucle de message vérifie continuellement les messages de Windows grâce à un 'Call GetMessage'. GetMessage passe une donnée à une structure de MESSAGE de Windows. Cette structure de MESSAGE sera remplie de l'information du message que Windows veut envoyer à une fenêtre dans le module. La fonction GetMessage ne retournera pas (ne reviendra pas à votre programme mais au contraire restera dans Kernel ou User) tant qu'un message (ex : appui sur un bouton) ne sera pas transmis pour une fenêtre dans le module. Pendant ce temps-là, Windows peut donner le contrôle à d'autres programmes. C'est en quelque sorte le fonctionnement en Multigestion de la plate-forme Win16. GetMessage renverra une erreur si le Message WM_QUIT est reçu dans la boucle de message, et ainsi nous termineront la boucle et irons vers la sortie du programme.
TranslateMessage est une fonction utile qui saisie les entrées de clavier (à la volée) et produit un nouveau Message WM_CHAR qui est placé sur la file d'attente des messages. Le message avec WM_CHAR contient la valeur ASCII pour la touche pressée, c'est bien plus facile de procéder ainsi. Vous pouvez omettre cet appel si votre programme ne traite pas de frappes.
'DispatchMessage' redirige les données d'un message( ex : envoi : on vient d'appuyer sur le bouton) à la procédure responsable de la fenêtre spécifique.

mov eax,msg.wParam
ret
WinMain endp

Si la boucle de message se termine, le code de sortie est stocké dans le membre wParam de la structure de MESSAGE. Vous pouvez stocker ce code de sortie dans eax pour le rendre à Windows. Actuellement, Windows ne se sert pas de la valeur de retour, mais c'est mieux de respecter les règles.

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

C'est notre procédure Win. Vous ne devez pas la nommer WndProc. Le premier paramètre, hWnd, est l' Handle de la fenêtre pour laquelle le message est destiné. uMsg est le message. Notez qu'uMsg n'est pas une structure de MESSAGE. C'est juste un numéro(nombre). Windows définit des centaines de messages, dont la plupart ne seront pas intéressants pour vos programmes. Windows enverra un message approprié à une fenêtre seulement si quelque chose d'intéressant arrive à cette fenêtre. La procédure qui traite vos fenêtres reçoit le message et y réagit intelligemment. wParam et lParam sont juste des paramètres supplémentaires pour l'utilisation de certains messages. Certains messages envoient des données d'accompagnement en plus du message lui-même. On passe ces données à la procédure de fenêtre au moyen de lParam et wParam.

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

Vient ici la partie cruciale. C'est l'endroit où la plupart de l'intelligence de votre programme réside. Les codes qui répondent à chaque message de Windows sont dans la procédure de fenêtre (la procédure qui s'occupe du contrôle de votre fenêtre). Votre code doit vérifier le message de Windows pour voir si c'est un message intéressant. S'il l'est, faites tout ce que vous voulez faire en réponse à ce message et retourner ensuite la valeur zéro dans eax. S'il ne l'est pas, vous DEVEZ appeler l'API 'DefWindowProc', pour lui passer tous les paramètres que vous y avez reçu pour les traiter par défaut .'DefWindowProc' est une fonction API qui traite les messages qui ne sont pas intéressant pour votre programme.
Le seul message que vous pouvez et DEVEZ envoyer vers votre procédure qui traite les messages est WM_DESTROY. Ce message est envoyé à votre procédure de fenêtre pour la refermée. Au moment où votre procédure de fenêtre reçoit ce message, votre fenêtre est déjà enlevée de l'écran. C'est juste un renseignement comme quoi votre fenêtre a bien été détruite, vous devez vous préparer à retourner à Windows. Si vous utilisez cela, vous n'avez d'autres choix que de quitter. Si vous voulez avoir une chance d'arrêter la fermeture de votre fenêtre, vous devez utiliser le message WM_CLOSE. Maintenant revenons à WM_DESTROY, après cette fermeture radicale, vous devez appeler PostQuitMessage qui postera WM_QUIT en retour à votre module(traitement des message). WM_QUIT fera le retour de GetMessage avec la valeur zéro dans eax, qui à son tour, terminera la boucle de message et quittera Windows. Vous pouvez envoyer le message WM_DESTROY à votre propre procédure de fenêtre en appelant la fonction DestroyWindow.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 4: Painting with Text

Tutorial 4: Dessiner du texte
(Painting with Text)

Dans ce tutorial, nous apprendrons comment "peindre" le texte dans 'le secteur client' (la zone de travail) d'une fenêtre. Nous verrons aussi tout le contexte autour de cette fonction. Vous pouvez télécharger le code source ici.

Théorie:

Le texte dans Windows est un type d'objet du GUI (NdT : Graphic User Interface). Chaque caractère est composé de plusieurs pixels (les points) qui sont regroupés dans un ordre particulier. C'est pourquoi on peut appeler "painting" au lieu de writting". Normalement, vous peignez le texte dans votre propre secteur client (en réalité, vous pouvez peindre le secteur client extérieur mais c'est une autre histoire). L'affichage du texte sur l'écran sous Windows diffère résolument du DOS. Sous DOS, la dimension de l'écran est 80x25. Mais sous Windows, l'écran est partagé par plusieurs programmes. Des règles doivent être prises pour éviter que certains programmes s'affichent par dessus l'application d'autres programmes. Windows contrôle ceci en limitant la peinture du secteur de chaque fenêtre à son propre secteur client seulement. La taille du secteur client d'une fenêtre n'est pas aussi rigide. L'utilisateur peut changer sa taille n'importe quand. Donc vous devez déterminer les dimensions de votre propre secteur client.
Avant que vous ne puissiez peindre quelque chose sur le secteur client, vous devez demander la permission à Windows. C'est ça, vous n'avez pas le contrôle absolu de l'écran comme si vous étiez sous DOS. Vous devez demander à Windows la permission de peindre votre propre secteur client. Windows déterminera la taille de votre secteur client, la taille des caractères, les couleurs et les autres GDI attribués et ensuite renvoie à votre programme l'Handle du 'contexte de ce dispositif' (de tout ça). Vous pourrez alors employer le 'contexte de dispositif' comme un passeport à la peinture sur votre 'secteur client'.
C'est quoi le 'contexte de dispositif' ? C'est juste une structure de données conçue à intérieure de Windows (pas accessible pour nous). Un 'contexte de dispositif' est associé à un dispositif particulier, comme une imprimante ou l'affichage vidéo. Pour une exposition vidéo, un contexte de dispositif est d'habitude associé à une fenêtre particulière en exposition.
Quelques-unes des valeurs dans le contexte de dispositif sont des attributs graphiques comme des couleurs, la font etc. Ce sont des valeurs par défaut que vous pouvez changer à volonté. Il est possible pour réduire la charge de travail de spécifier ces attributs dans chaques appels de fonction GDI.
Vous pouvez vous représenter un 'contexte de dispositif' comme un 'environnement par défaut' mis à votre disposition par Windows. Vous pouvez ne pas tenir compte de certains éléments par défaut puis y revenir plus tard si vous le souhaitez.
Quand un programme a besoin de peindre, il doit obtenir un 'Handle de contexte de dispositif'. Normalement, il y a plusieurs façons de l'obtenir.
    Appelez BeginPaint en réponse au message WM_PAINT.
    Appelez GetDC en réponse à d'autres messages.
    Appelez CreateDC pour créer votre propre contexte de dispositif.
Rappeler vous une chose, après avoir obtenu un 'Handle de contexte de dispositif', vous devez le sortir pendant le traitement d'un simple message. Ne récupérez pas un Handle en réponse à un message, transférez-le en réponse à un autre message.
Windows envoie des messages WM_PAINT à une fenêtre pour lui indiquer qu'il est maintenant temps de repeindre son secteur client. Windows ne sauve pas le contenu du secteur client de la fenêtre, mais au lieu de cela, quand une situation arrive et garantit une nouvelle peinture du secteur client (comme quand une fenêtre a été couverte par une autre et vient la recouvrir), Windows met le message WM_PAINT dans la file d'attente des messages de cette fenêtre. C'est à cette fenêtre de repeindre son propre secteur client. Vous devez réunir toute l'information qui sert à repeindre votre secteur client dans la section WM_PAINT de votre procédure de fenêtre, ainsi la procédure de fenêtre (la procédure qui gère l'affichage et le comportement de votre fenêtre) pourra repeindre le secteur client quand le message WM_PAINT arrivera.
Un autre concept que vous devez prendre en compte est une petite zone rectangle. Windows définit un rectangle invalide comme le plus petit secteur rectangulaire dans le secteur client qui a besoin d'être repeint (rafraîchi) . Quand Windows détecte un rectangle invalide dans le secteur client d'une fenêtre, il envoie le message WM_PAINT à cette fenêtre. En réponse à ce message WM_PAINT, la fenêtre peut obtenir une structure 'Paintstruct' qui contient, entre autre, les coordonnées du rectangle invalide. Vous appelez 'BeginPaint' en réponse au message WM_PAINT pour valider le rectangle invalide. Si vous ne traitez pas le message WM_PAINT, alors vous devez appeler DefWindowProc ou ValidateRect pour valider le rectangle invalide sinon à plusieurs reprises Windows vous enverra le message WM_PAINT. Laissez tombé ça, c'est trop confus.
Voici ci-dessous les étapes que vous devez exécuter en réponse à un message WM_PAINT :
    Obtenez l' 'handle du contexte de dispositif' avec BeginPaint.
    Repeignez le secteur client (la zone de travail de votre fenêtre).
    Libérez l' 'handle du contexte de dispositif' avec EndPaint.
Notez que vous ne devez pas explicitement valider le rectangle invalide. C'est le Call BeginPaint qui s'en occupe automatiquement. Entre la paire de BeginPaint-Endpaint, vous pouvez appeler n'importe quelles fonctions du GDI pour peindre votre secteur client. Presque tous exigent l' 'handle du contexte de dispositif' en tant que paramètre.

Contenu:

Nous allons écrire un programme qui fait apparaître le texte " Win32 assembly is great and easy!" au le centre du secteur client.
.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
OurText db "Win32 assembly is great and easy!",0

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.CODE
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke GetClientRect,hWnd, ADDR rect
invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax, eax
ret
WndProc endp
end start

Analyse:

Le code dans sa grande majorité est le même que celui de l'exemple du Tutorial 3. J'expliquerai donc seulement les changements importants.

LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT

Voici les variables locales qui sont employées par des fonctions du GDI dans notre section WM_PAINT. 'Hdc' est employé pour stocker l'handle du contexte de dispositif renvoyé après l'appel de la fonction BeginPaint. 'ps' est une structure de PAINTSTRUCT. Normalement ce n'est pas vous qui donnez une valeur à ps. On le passe à la fonction BeginPaint et Windows lui donne la valeur appropriée. De la même façon, vous passez 'ps' à la fonction EndPaint quand vous finissez de repeindre le secteur client. 'rect' est une structure rect définie comme suit :

RECT Struct
left LONG ?
top LONG ?
right LONG ?
bottom LONG ?
RECT ends
Left et top sont les coordonnées du coin supérieur gauche d'un rectangle Right and bottom sont les coordonnées du coin inférieur droit. Une chose à se souvenir : l'origine des axes x-y est au coin supérieur gauche du secteur client. Donc le point y=10 est AU-DESSOUS du point y=0.

invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke GetClientRect,hWnd, ADDR rect
invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,hWnd, ADDR ps

En réponse au message WM_PAINT, vous appelez BeginPaint avec la structure PAINTSTRUCT non initialisée et l'handle de la fenêtre que vous souhaitez peindre, comme paramètres. Après un appel couronné de succès, eax contient l'Handle du contexte de dispositif. Ensuite vous appelez GetClientRect pour retrouver la dimension du secteur client. La dimension est renvoyée dans la variable rect que vous passez à DrawText comme un de ses paramètres. La syntaxe de DrawText est :

DrawText proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD

DrawText est une production de texte de haut niveau créé par une fonction API. Il manipule quelques renseignements peu coutumiers tels que le 'word wrap' (l'enveloppe autour des mots), ou le 'centering' (l'allignement au centre) etc… que vous pourriez appliquez sur le texte que vous souhaitez peindre. Son équivalent de bas niveau, TextOut, sera examiné dans le Tutorial suivant. DrawText définit un texte pour qu'il loge dans les limites d'un rectangle. Il emploie la fonte (la taille, la police) actuellement choisie, la couleur et le fond (dans le contexte de dispositif) pour dessiner le texte. Les lignes sont encadrées pour rester dans les limites du rectangle. Il rend la hauteur du texte produit dans les unités du dispositif, qui dans notre cas sont en pixels. Regardons de plus près ces paramètres :

    hdc est l'handle du contexte de dispositif
    lpString est le pointer de la chaîne de caractères que vous voulez dessiner dans le rectangle. Cette chaîne de caractères doit être terminée par un NULL (0) sinon avec l'autre méthode vous devez spécifier sa longueur dans le paramètre suivant, nCount.
    nCount est le nombre de caractères à afficher. Si la chaîne de caractères est terminée par un NULL (cas précédent), nCount doit être mis à -1. Autrement nCount doit contenir le nombre de caractères de la chaîne que vous voulez dessiner.
    lpRect est le pointer du rectangle (de l'objet de type RECT) que vous voulez dessiner contenant la chaîne de caractères. Notez que ce rectangle est aussi un rectangle de coupure, c'est-à-dire que vous ne pouvez pas dessiner le texte à l'extérieur de ce rectangle.
    uFormat est la valeur qui indique comment la chaîne de caractère est insérée dans le rectangle. Nous employons trois valeurs combinées par l'opérateur "or" :
    • DT_SINGLELINE indique une simple (une unique)ligne de texte.
    • DT_CENTER centre le texte horizontallement.
    • DT_VCENTER centre le texte verticalement. Il doit être employé avec DT_SINGLELINE.
Après que vous ayez fini de repeindre le secteur client, vous devez appeler la fonction EndPaint pour libérer l'handle du contexte de dispositif.
Voilà. Nous pouvons récapituler les points importants:
  • On appelle la paire BeginPaint - EndPaint en réponse au message WM_PAINT.
  • Faites tout ce que vous voulez dans le secteur client entre les appels BeginPaint et EndPaint.
  • Si vous voulez repeindre votre secteur client en réponse à d'autres messages, vous avez deux choix :
    (Si il y a une communication entre l'ordi et vous et que vous n'êtes pas en mode texte mais en mode Painting, vous avez deux choix)
    • Employez la paire GetDC - ReleaseDC et faîtes votre 'painting' entre ces 2 appels.
    • Appelez InvalidateRect ou UpdateWindow; pour préserver le secteur client entier de toute écriture, forçant ainsi Windows à renvoyer le message WM_PAINT dans la file d'attente de message de votre fenêtre et faites votre peinture dans la section WM_PAINT.
[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 5: More about Text

Tutorial 5: Encore plus
A propos des Textes

Allons plus loin avec les attributs de texte, c'est-à-dire. La fonte (Fonte = Assortissement complet de caractères de même type) et la couleur.

Downloadez le fichier en exemple ici.

Théorie:

Le système de couleur de Windows est basé sur le système RGB, R=red, G=Green, B=Blue. Si vous souhaitez définir une couleur dans Windows, vous devez exprimer votre couleur recherchée en combinant ces trois couleurs principales. Chaque plage de couleur (R,G, ou B) a une gamme de 0 à 255 (valeur exprimée en octet). Par exemple, si vous voulez la couleur rouge pur, vous devez employer 255,0,0. Ou si vous voulez la couleur blanc pur, vous devez employer 255,255,255. Vous verrez en expérimentant, qu'obtenir la couleur dont vous avez besoin est très difficile avec ce système puisque vous devez avoir un bon 'felling' pour mélanger les couleurs correspondantes.
Pour la couleur du texte et du fond, vous employez SetTextColor et SetBkColor, tous deux exigent l'handle du contexte de dispositif et une valeur RGB sur 32 bits. La structure de la valeur 32 bits RGB est définie ainsi :

RGB_value struct
unused db 0
blue db ?
green db ?
red db ?
RGB_value ends

Notez que le premier octet n'est pas utilisé et doit être à zéro. L'ordre des trois octets restant est inversé, il s'agit du Bleu, vert, rouge (et non pas RGB). Cependant, nous n'emploierons pas cette structure puisque elle est encombrante à initialiser et à employer. Nous créerons une macro à la place. La macro recevra trois paramètres : les valeurs rouges, vertes et bleues. Ceci produira la valeur RGB désirée, et le stockera sur 32 bits dans eax. La macro est la suivante :

RGB macro red,green,blue
xor eax,eax
mov ah,blue
shl eax,8
mov ah,green
mov al,red
endm

Vous pouvez mettre cette macro dans le fichier INCLUDE pour une utilisation future.
Vous pouvez "créer" une fonte en appelant CreateFont ou CreateFontIndirect. La différence entre ces deux fonctions est que CreateFontIndirect reçoit seulement un paramètre : 'un indicateur logique sur la structure de la fonte', à savoir : LOGFONT. CreateFontIndirect est le plus flexible de deux en particulier si vos programmes ont besoin de changer fréquemment de fontes. Cependant, dans notre exemple, nous "créerons" seulement une fonte pour la démonstration, nous échapperons donc à CreateFont. Après l'appel de CreateFont, il renverra l'handle d'une fonte que vous devez choisir dans le contexte de dispositif. Après cela, chaque texte affiché par la fonction API (Paint) emploiera la fonte que nous avons choisie dans le contexte de dispositif.

Contenu:

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

RGB macro red,green,blue
xor eax,eax
mov ah,blue
shl eax,8
mov ah,green
mov al,red
endm

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
TestString db "Win32 assembly is great and easy!",0
FontName db "script",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
nbsp; WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL hfont:HFONT

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\
ADDR FontName
invoke SelectObject, hdc, eax
mov hfont,eax
RGB 200,200,50
invoke SetTextColor,hdc,eax
RGB 0,0,255
invoke SetBkColor,hdc,eax
invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString
invoke SelectObject,hdc, hfont
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

end start

Analyse:

invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\
ADDR FontName

CreateFont crée une fonte logique(mélangée RGB) qui est la couleur la plus proche des paramètres sélectionnés et des données de fonte disponibles. Cette fonction a plus de paramètres qu'aucune autre fonction de Windows. Il renvoie l'handle de la fonte logique qui doit être utilisé par la fonction SelectObject. Nous allons examiner ces paramètres en détail.

CreateFont proto nHeight:DWORD,\
nWidth:DWORD,\
nEscapement:DWORD,\
nOrientation:DWORD,\
nWeight:DWORD,\
cItalic:DWORD,\
cUnderline:DWORD,\
cStrikeOut:DWORD,\
cCharSet:DWORD,\
cOutputPrecision:DWORD,\
cClipPrecision:DWORD,\
cQuality:DWORD,\
cPitchAndFamily:DWORD,\
lpFacename:DWORD

nHeight est la hauteur désirée des caractères. On prend 0 par défaut pour utiliser la taille moyenne.
nWidth est la largeur désirée des caractères. Normalement cette valeur doit être 0,ce qui permet à Windows de faire correspondre la largeur à la hauteur. Cependant, dans notre exemple, la largeur par défaut fait que les caractères sembles difficile à lire, donc on emploiera la largeur 16 à la place de cela.
nEscapement Sert à spécifier l'inclinaison des caractères qui suivent cette instruction. On compte en dixième de degré. Normalement, cette valeur est à 0. On la met à 900 pour avoir tous les caractères va vers le haut, à 1800 pour écrire vers l'arrière, ou 2700 pour écrire chaque caractère du haut vers le bas.
nOrientation Indique de combien doivent être tournés les caractères (en dixième de degré). 900 pour avoir tous les caractères couchés tournés vers le haut, 1800 pour le contraire, etc.
nWeight Donne l'épaisseur de chaque caractère. Windows définit les tailles suivantes :

    FW_DONTCARE equ 0
    FW_THIN equ 100
    FW_EXTRALIGHT equ 200
    FW_ULTRALIGHT equ 200
    FW_LIGHT equ 300
    FW_NORMAL equ 400
    FW_REGULAR equ 400
    FW_MEDIUM equ 500
    FW_SEMIBOLD equ 600
    FW_DEMIBOLD equ 600
    FW_BOLD equ 700
    FW_EXTRABOLD equ 800
    FW_ULTRABOLD equ 800
    FW_HEAVY equ 900
    FW_BLACK equ 900
cItalic 0 pour normal, une autre valeur pour caractères italiques.
cUnderline 0 pour normal, une autre valeur pour les caractères soulignés.
cStrikeOut 0 pour normal, une autre valeur pour des caractères (barrés) avec une ligne par le centre.
cCharSet C'est le jeu de caractères de la fonte (ex : ARIAL). Ce doit normalement être OEM_CHARSET, ce qui permet à Windows de choisir la fonte.
cOutputPrecision Indique à quel point la fonte choisie doit être proche des caractéristiques que nous voulons. Doit normalement on prend OUT_DEFAULT_PRECIS par défaut.
cClipPrecision Indique la précision de la coupure. La coupure c'est ce qui définit les bords de la zone d'écriture. Le texte sera coupé en arrivant près des bords et cette zone de coupure peut être plus ou moins étendue dans l'intérieur du secteur client. Vous pouvez utiliser la valeur CLIP_DEFAULT_PRECIS qui définit le comportement de coupure par défaut.
cQuality Indique la qualité de production. La qualité de production définit précisément comment le GDI doit essayer de calquer au mieux les attributs de la fonte logique à ceux d'une fonte physique réelle. Il y a trois choix : DEFAULT_QUALITY, PROOF_QUALITY et DRAFT_QUALITY.
cPitchAndFamily Spécifie la hauteur et la type de fonte. Vous devez combiner ces 2 valeurs avec l'opérateur "or".
lpFacename C'est un NULL qui indique la police de caractères de la fonte.

La description ci-dessus n'est en aucun cas complète. Vous devez consulter votre référence API Win32 pour plus de détails.

invoke SelectObject, hdc, eax
mov hfont,eax

Après avoir obtenu l' handle de la fonte logique, nous devons l'employer pour choisir la fonte dans le contexte de dispositif en appelant SelectObject. SelectObject met les nouveaux objets du GDI tels que les stylos, les brosses et les fontes dans le contexte de dispositif pour être employés par des fonctions du GDI. Il renvoie l' handle (de l'objet à replacé) que nous devons sauvegarder pour de futures appels de la fonction SelectObject. Après l'appel SelectObject, n'importe quelle fonction de production de texte emploiera la fonte que nous venons juste de choisir dans le contexte de dispositif.

RGB 200,200,50
invoke SetTextColor,hdc,eax
RGB 0,0,255
invoke SetBkColor,hdc,eax

Employez la macro RGB pour créer une valeur RGB sur 32 bits pour être employée par SetColorText et SetBkColor.

invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString

Appelez la fonction TextOut pour dessiner le texte sur le secteur client. Le texte sera dans la fonte et la couleur que nous avons spécifié précédemment.

invoke SelectObject,hdc, hfont

Quand on as fini de se servir de cette fonte, on doit rétablir l'ancienne fonte dans le contexte de dispositif. Vous devez toujours rétablir l'objet que vous avez remplacé dans le contexte de dispositif.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 6: Keyboard Input

Tutorial 6: Les Entrées Clavier

Nous allons voir comment un programme Windows traite les entrées clavier.

Downloadez le fichier en example ici.

Théorie:

Puisque normalement il y a seulement un clavier pour chaque PC, tous les programmes Windows en action doivent le partager entre eux. Windows est responsable de l'envoie des frappes de touches vers la bonne fenêtre qui attend ces entrées.
Bien qu'il puisse y avoir plusieurs fenêtres sur l'écran, seule une d'entre elles a temporairement la possibilité de traiter les entrées clavier. La fenêtre qui traite les entrées est la seule qui peut recevoir des frappes de touches. Vous pouvez différencier la fenêtre qui traite les entrées des autres fenêtres en regardant la barre de titre. La barre de titre de cette fenêtre est activée.
En réalité, il y a deux principaux types de messages pour le clavier, selon votre vision du clavier. Vous pouvez voir un clavier comme un ensemble de touches. Dans ce cas, si vous pressez une touche, Windows envoie le message WM_KEYDOWN à la fenêtre qui traite les entrées clavier, signifiant ainsi qu'une touche a été enfoncée. Quand vous relâchez cette touche, Windows envoie le message WM_KEYUP. Vous traitez une touche de la même manière qu'un bouton. L'autre façon de voir le clavier consiste en ce que c'est un dispositif d'entrée de caractères. Quand vous appuyez sur la touche "a", Windows envoie un message WM_CHAR à la fenêtre qui traite les entrées, disant ainsi que l'utilisateur envoie le caractère "a". En fait, Windows envoie les messages WM_KEYDOWN et WM_KEYUP à la fenêtre qui traite les entrées et ces messages seront traduis en messages WM_CHAR par l'appel de TranslateMessage. La procédure qui s'occupe du fonctionnement de votre fenêtre peut décider de traiter les trois messages ou bien seulement les messages auxquels vous vous intéressez. La plupart du temps, vous pouvez ignorer WM_KEYDOWN et WM_KEYUP puisque la fonction TranslateMessage appelle la boucle de message qui traduit les messages WM_KEYDOWN et WM_KEYUP en message WM_CHAR. Nous nous concentrerons donc uniquement sur WM_CHAR dans ce Tutorial.

Example:

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
char WPARAM 20h ; Le caractère que le programme reçoit du clavier.
; 20h est la valeur pour obtenir un espace.
; Ainsi rien ne sera affiché à l'écran au moment de son ouverture.

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke TextOut,hdc,0,0,ADDR char,1
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

char WPARAM 20h ; Le caractère que le programme reçoit du clavier

C'est la variable qui stockera le caractère envoyé par le clavier. Puisque le caractère est envoyé dans le paramètre WPARAM (appartenant à la procédure de fenêtre). La valeur initiale de WPARAM est 20h pour afficher un 'espace' au moment où notre fenêtre régénère son secteur client la toute première fois, quand il n'y a aucune entrée clavier. Donc au commencement, nous voulons afficher 'espace'.

.ELSEIF uMsg==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE

Ceci a été rajouté à la procédure de fenêtre pour manipuler le message WM_CHAR. Il met seulement le caractère dans la variable nommée "char" et appelle ensuite InvalidateRect. InvalidateRect fait apparaître le 'rectangle invalide' dans le secteur client et force ainsi Windows à envoyer le message WM_PAINT à la procédure de fenêtre. Sa syntaxe est la suivante :

InvalidateRect proto hWnd:HWND,\
lpRect:DWORD,\
bErase:DWORD

lpRect est un indicateur sur le rectangle qui se trouve dans le secteur client que nous voulons déclarer comme étant invalide. Si ce paramètre est nul, le secteur client entier deviendra invalide.
bErase est un drapeau (un flag) indiquant à Windows s'il a besoin d'effacer le fond (background). Si ce drapeau est VRAI, alors Windows effacera le background du rectangle invalide dès que BeginPaint sera appelé.

Ainsi la stratégie que nous avons employé ici est que : nous stockons toute l'information nécessaire touchant à la peinture du secteur client et fabriquons ensuite le message WM_PAINT pour repeindre le secteur client. Bien sûr, les codes dans la section WM_PAINT doivent savoir à l'avance ce que l'on attend d'eux. On pourrait croire qu'on tourne en rond mais c'est la méthode utilisée par Windows.
En réalité nous pouvons repeindre (réactualiser) le secteur client pendant le traitement du message WM_CHAR en appelant la paire GetDC - ReleaseDC. A ce niveau, il n'y a aucun problème. Mais ça commence à être intéressant quand notre fenêtre a besoin de repeindre son secteur client. Puisque les codes qui peignent (affichent) le caractère sont dans la section WM_CHAR, la procédure de fenêtre ne sera plus capable de repeindre notre caractère sur le secteur client. Donc la ligne suivante sert à mettre toutes les données et les codes nécessaires à peinture dans WM_PAINT. Vous pouvez envoyer le message WM_PAINT n'importe où dans votre code dès que vous voulez repeindre le secteur client.

invoke TextOut,hdc,0,0,ADDR char,1

Quand InvalidateRect est appelé, il envoie un message WM_PAINT à la procédure de fenêtre en retour. Donc les codes dans la section WM_PAINT sont appelés. Il appelle BeginPaint comme d'habitude pour obtenir l'handle du contexte de dispositif et appelle ensuite TextOut qui dessine notre caractère dans le secteur de client à x=0, y=0. Quand vous exécutez le programme et appuyez sur une touche, vous verrez que le caractère se répercute dans le coin supérieur gauche de la fenêtre. Et quand la fenêtre est rétrécie au maximum puis réagrandie de nouveau, le caractère est toujours présent puisque tous les codes et toutes les données essentielles pour repeindre sont tous réunis dans la section WM_PAINT.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 7: Mouse Input

Tutorial 7: Les entrées Souris

Nous allons voir comment recevoir et répondre aux entrées souris dans notre procédure de fenêtre. Le programme d'exemple contrôlera les cliques gauche de la souris et affichera un texte sur le secteur client aux coordonnées même de la souris.

Downloadez le fichier en example ici.

Théorie:

Comme pour les entrées clavier, Windows détecte les activités de la souris dépendantes de chaque fenêtre. Ici notre activité portera uniquement sur le clique gauche et sur le mouvement du curseur de la souris dans la fenêtre. À la différence des entrées clavier qui sont adressés uniquement à la fenêtre qui est activée, les messages de la souris sont envoyés à n'importe quelle fenêtre (active ou non) du moment que la souris soit placée dessus. En plus, il existe aussi des 'messages souris' adressés au secteur non-client. Mais la plupart du temps, nous pouvons les ignorer sans s'en soucier. On peut se concentrer pleinement sur les messages touchant au secteur client.
Il y a deux messages pour chaque bouton de la souris : WM_LBUTTONDOWN, WM_RBUTTONDOWN et WM_LBUTTONUP, WM_RBUTTONUP. Pour une souris avec trois boutons, il y a aussi WM_MBUTTONDOWN et WM_MBUTTONUP. Pour le déplacement du curseur de souris sur le secteur client, Windows envoie le message WM_MOUSEMOVE à la fenêtre qui se trouve sous le curseur.
Une fenêtre peut recevoir des messages de double-clic, WM_LBUTTONDBCLK ou WM_RBUTTONDBCLK, si et seulement si sa classe de fenêtre obtient le drapeau (flag) de style CS_DBLCLKS, sinon la fenêtre ne recevra seulement qu'une série de messages disant que les boutons de souris on étés pressés (Button Up, Button Down).
Pour tous ces messages, la valeur de lParam contient (sera) la position de la souris. On travaille en 32 Bit donc sur des doubles mots. Le mot de poids faible sera la coordonnée X et le mot de poids fort sera la coordonnée Y à partir du coin supérieur gauche du secteur client de la fenêtre. WParam indique l'état des boutons de la souris, et des touches Shift et Ctrl.

Example:

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MouseClick db 0 ; 0=pas encore de click

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hitpoint POINT <>

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_LBUTTONDOWN
mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax
mov eax,lParam
shr eax,16
mov hitpoint.y,eax
mov MouseClick,TRUE
invoke InvalidateRect,hWnd,NULL,TRUE
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
.IF MouseClick
invoke lstrlen,ADDR AppName
invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax
.ENDIF
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

.ELSEIF uMsg==WM_LBUTTONDOWN
mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax
mov eax,lParam
shr eax,16
mov hitpoint.y,eax
mov MouseClick,TRUE
invoke InvalidateRect,hWnd,NULL,TRUE

La procédure de fenêtre attend qu'on clique sur le bouton gauche de la souris. Quand il reçoit WM_LBUTTONDOWN, lParam prend la valeur de la coordonnée du curseur de la souris dans le secteur client. Il sauvegarde la coordonnée dans une variable de type POINT qui est défini comme ceci:

POINT STRUCT
x dd ?
y dd ?
POINT ENDS

et le 'flag' qui contrôle l'état de MouseClick, est mis à TRUE (VRAI), signifiant ainsi qu'il y a au moins le bouton gauche de la souris qu'on peut cliquer sur le secteur client.

mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax

Puisque la coordonnée X est le mot de poids faible du double mot lParam et que les 2 membres qui forment la structure du POINT sont d'une taille de 32 bits, nous devons mettre la partie haute de 'eax' à 0 avant le stockage de celle-ci.

shr eax,16
mov hitpoint.y,eax

Puisque la coordonnée Y est le mot de poids fort du double mot lParam, nous devons mettre la partie basse de 'eax' à 0 avant le stockage de celle-ci. Nous pouvons faire ça en déplaçant 16bits de eax vers la droit.
Après le stockage de la position de la souris, nous mettons le flag contrôlant l'état de MouseClick à TRUE (VRAI) pour que le code de la section WM_PAINT sache qu'il y a au moins un clique effectué sur le secteur client. Donc il peut dessiner la chaîne de caractère à la position de la souris. Ensuite nous appelons la fonction InvalidateRect pour forcer la fenêtre à repeindre la totalité de son secteur client.

.IF MouseClick
invoke lstrlen,ADDR AppName
invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax
.ENDIF

Le code de la section WM_PAINT (celui qui sert à repeindre le secteur client de votre fenêtre) doit vérifier si MouseClick est vrai, dès que la fenêtre a été créée. Au tout début il a reçu un message WM_PAINT, pour lequel il n'y avait aucun clique de souris, ainsi il ne doit pas dessiner la chaîne de caractère dans le secteur client. Nous initialisons donc MouseClick à FALSE (FAUX), mais changeons sa valeur à TRUE (VRAI) uniquement quand advient un réel clique de souris.
Si au moins un clique de souris arrive, il dessine la chaîne de caractère sur le secteur client à la position de la souris. Notez qu'il appelle 'lstrlen' pour obtenir la longueur de la chaîne à afficher et renvoie la longueur comme dernier paramètre de la fonction TextOut.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 8: Menu

Tutorial 8: Le Menu

Dans ce tutorial, nous allons apprendre comment incorporer un menu à notre fenêtre.
Downloadez l'example 1 et l'example2.

Theorie:

Le menu est un des composants le plus important d'une votre fenêtre. Le menu présente la liste des services qu'un programme peut offrir à l'utilisateur. L'utilisateur ne doit pas lire le manuel inclus avec le programme pour être capable de l'employer, il lui suffit de lire attentivement le menu pour obtenir une vue d'ensemble de la capacité d'un programme particulier et commencer à jouer avec lui immédiatement. Puisque un menu est un outil pour que l'utilisateur puisse rapidement se diriger, vous devez suivre la norme. Succinctement, les deux premiers articles du menu doivent être : le fichier et d'Edition et le fichier d'Aide. Vous pouvez insérer vos propres articles de menu entre celui d'Edition et celui de l'Aide. Si un article du menu invoque une boîte de dialogue, vous devez ajouter un ellipsis (...) à la chaîne de caractère du menu.
Le menu est une sorte de ressource. Il y a plusieurs sortes de ressources tels que la boîte de dialogue, les tableaux de caractères, l'icône, bitmap, le menu etc. Les ressources sont décrites dans un fichier séparé, appelé fichier de ressources et qui normalement a l'extension .rc. Vous combinez alors les ressources avec le code source pendant l'étape de liaison. Le produit final est un fichier exécutable qui contient, à la fois les instructions et les ressources.
Vous pouvez écrire des scénarios de ressources en employant n'importe quel éditeur de texte. Ces fichiers ressources sont composés des expressions qui décrivent les convenances ainsi que d'autres attributs des ressources employées dans un programme particulier. Bien que vous puissiez écrire des scénarios de ressource avec un éditeur de texte, c'est plutôt encombrant. Une meilleure alternative est d'employer un éditeur de ressources qui vous laisse concevoir visuellement des ressources avec un bien meilleurs confort. Les éditeurs de ressources sont habituellement inclus dans le pack avec le compilateur comme C Visuel +, Borland C +, etc.
Nous pouvons décrire une ressource de menu comme ceci :
    MyMenu MENU
    {
    [Ici, la liste du menu]
    }
Les programmeurs sous C peuvent se rendre compte que c'est semblable à la déclaration de la structure MyMenu d'un nom de menu suivi par le mot clef MENU et le menu est inscrit entre parenthèses[]. Alternativement, vous pouvez employer 'BEGIN' et 'END' au lieu des parenthèses si vous préférez. Cette syntaxe est plus acceptable pour les programmeurs en Pascal.
La liste du menu peut être soit MENUITEM soit la déclaration POPUP.
La déclaration MENUITEM définit une barre(bar) de menu qui n'invoque pas de menu popup lorsqu'il est choisi. La syntaxe est la suivante :
    MENUITEM "&text", ID [,options]
Il commence par le mot-clé MENUITEM suivi du texte que vous voulez employer en tant que chaîne de caractères pour la barre de menu. Notez l'esperluète. Il sert à souligner le caractère qui le suit. Après la chaîne de caractère formant le texte on trouve le n°ID de l'article du menu. L'ID est le numéro qui sera employé pour identifier l'article du menu dans le message envoyé à la procédure de fenêtre lorsqu'on le sélectionne. Chaque n°ID du menu doit être unique vis à vis des autres.
'Options' justement en option. Les options disponibles sont les suivantes :
  • GRAYED L'article du menu est inactif et il ne produit pas de message WM_COMMAND. Le texte est grisé (grayed).
  • INACTIVE L'article du menu est inactif et il ne produit pas de message WM_COMMAND. Le texte est quand même affiché normalement.
  • MENUBREAK Cet article et les articles suivants apparaissent sur une nouvelle ligne du menu.
  • HELP Cet article et les articles suivants sont alignés à droite.
Vous pouvez employer une de ces options ou bien les combiner entre elles avec l'opérateur "or". Prenez garde à ce que INACTIVE et GRAYED ne soient pas utilisés ensembles.
La syntaxe de la déclaration POPUP est la suivante :
POPUP "&text" [,options]
{
[menu list]
}
La déclaration POPUP définit une barre de menu qui, quand elle est choisi, abaisse une liste d'articles de menu dans une petite fenêtre POPUP. La liste du menu peut être un MENUTIEM ou la déclaration POPUP. Il y a une sorte de déclaration spéciale qui est MENUITEM, MENUITEM est le SÉPARATEUR, qui dessinera une ligne horizontale à l'intérieur de la fenêtre POPUP.
L'étape suivante après que vous en ayez finis avec le scénario de ressources est que votre programme doit faire référence à votre menu.
Vous pouvez faire ceci à deux endroits différents dans votre programme.
  • Dans le membre lpszMenuName de la structure de WNDCLASSEX. Déclarez, que vous avez un menu nommé "FirstMenu", vous pouvez assigné le menu à votre fenêtre comme cela :
        • .DATA
          MenuName db "FirstMenu",0
        ...........................
        ...........................
        .CODE
          ...........................
          mov wc.lpszMenuName, OFFSET MenuName
          ...........................
  • Ou bien dans les paramètres de CreateWindowEx au niveau de l' handle du menu (Hmenu) comme cela :
        • .DATA
          MenuName db "FirstMenu",0
          hMenu HMENU ?
        ...........................
        ...........................
        .CODE
          ...........................
          invoke LoadMenu, hInst, OFFSET MenuName
          mov hMenu, eax
          invoke CreateWindowEx,NULL,OFFSET ClsName,\
          OFFSET Caption, WS_OVERLAPPEDWINDOW,\
          CW_USEDEFAULT,CW_USEDEFAULT,\
          CW_USEDEFAULT,CW_USEDEFAULT,\
          NULL,\
          hMenu,\
          hInst,\
          NULL\
          ...........................
Donc vous pouvez vous demander, quelle est la différence entre ces deux méthodes ?
Quand vous faites référence au menu dans la structure de WNDCLASSEX, le menu devient le menu "par défaut" pour la classe de fenêtre. Chaque fenêtre de cette classe aura le même menu.
Si vous souhaitez que chaque fenêtre ait la même classe mais qu'elles aient des menus différents, vous devez choisir la deuxième forme. Dans ce cas, n'importe quelle fenêtre dont on passe l'handle de son menu à la fonction CreateWindowEx aura un menu qui "ne tient pas compte" du menu par défaut, défini dans la structure WNDCLASSEX.
Ensuite nous devons examiner comment un menu fait pour indiquer à la procédure de fenêtre quand l'utilisateur choisit un des article du menu.
Quand l'utilisateur choisit un article du menu, la procédure de fenêtre reçoit le message WM_COMMAND. Le mot de poids faible de wParam contient le n°ID de l'article choisi dans le menu. (Le mot de poids faible de wParam = si c'est du contenu de eax (qui est sur 32Bit) dont on parle alors on parle plus précisément ici des 16 dernier bits de eax).
Maintenant nous avons les informations suffisantes pour créer et employer un menu. On va le faire.

Example:

Le premier exemple montre comment créer et employer un menu en spécifiant le nom de menu dans la classe de fenêtre.

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0 ; Le nom de notre menu dans le fichier de ressources.
Test_string db "You selected Test menu item",0
Hello_string db "Hello, my friend",0
Goodbye_string db "See you again, bye",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.const
IDM_TEST equ 1 ; (Menu IDs) ou: Les n°Identification des éléments constituants le Menu
IDM_HELLO equ 2
IDM_GOODBYE equ 3
IDM_EXIT equ 4

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName ; Place le nom de notre menu ici
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF ax==IDM_TEST
invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK
.ELSEIF ax==IDM_HELLO
invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK
.ELSEIF ax==IDM_GOODBYE
invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
**************************************************************************************************************************

Menu.rc
**************************************************************************************************************************

#define IDM_TEST 1
#define IDM_HELLO 2
#define IDM_GOODBYE 3
#define IDM_EXIT 4

FirstMenu MENU
{
POPUP "&PopUp"
{
MENUITEM "&Say Hello",IDM_HELLO
MENUITEM "Say &GoodBye", IDM_GOODBYE
MENUITEM SEPARATOR
MENUITEM "E&xit",IDM_EXIT
}
MENUITEM "&Test", IDM_TEST
}

Analyse:

On va analyser le fichier de ressources d'abord.
#define IDM_TEST 1 /* cette ligne revient au même que : IDM_TEST equ 1*/
#define IDM_HELLO 2
#define IDM_GOODBYE 3
#define IDM_EXIT 4
Les susdites lignes définissent les n°ID des éléments constituants le menu. Vous pouvez assigner n'importe quelle valeur à l'ID tant que cette valeur est unique dans le menu.

FirstMenu MENU

Déclarez votre menu avec le mot clef 'MENU'

POPUP "&PopUp"
{
MENUITEM "&Say Hello",IDM_HELLO
MENUITEM "Say &GoodBye", IDM_GOODBYE
MENUITEM SEPARATOR
MENUITEM "E&xit",IDM_EXIT
}

Définit un menu popup avec quatre articles de menu, le troisième est un séparateur de menu.

MENUITEM "&Test", IDM_TEST

Définit une barre de menu dans le menu principal.
Ensuite nous examinons la source du code.

MenuName db "FirstMenu",0 ; C'est le nom de notre menu dans le fichier de ressources.
Test_string db "You selected Test menu item",0
Hello_string db "Hello, my friend",0
Goodbye_string db "See you again, bye",0
MenuName est le nom du menu dans le fichier de ressources. Remarquez que vous pouvez définir plus qu'un menu dans le fichier de ressource. Donc vous devez spécifier quel menu vous voulez employer. Ces trois lignes définissent les chaînes de caractères à afficher dans les boîtes de message (Message Box) qui sont invoquées plus loin quand l'article approprié du menu est choisi par l'utilisateur.
IDM_TEST equ 1 ; (Menu IDs) Les n° d'identités des éléments constituants le Menu.
IDM_HELLO equ 2
IDM_GOODBYE equ 3
IDM_EXIT equ 4
Définit les 'menu IDs' pour pouvoir les utiliser dans la procédure de fenêtre. Ces valeurs DOIVENT ÊTRE identiques à celles définies dans le fichier de ressources.

.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF ax==IDM_TEST
invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK
.ELSEIF ax==IDM_HELLO
invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK
.ELSEIF ax==IDM_GOODBYE
invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF

Dans la procédure de fenêtre, nous traitons des messages WM_COMMAND. Quand l'utilisateur choisit un article du menu, le menu ID de cet article est envoyé à la procédure de fenêtre dans le mot de poids faible de wParam grâce au message WM_COMMAND. Ainsi quand nous stockons la valeur de wParam dans eax, on compare sa valeur avec eax au niveau des 'menu IDs' que nous avons définis précédemment et le programme agit en conséquence. Dans les trois premiers cas, quand l'utilisateur sélectionne les articles Test, Say Hello, and Say GoodBye du menu, uniquement une seule chaîne de caractères s'affiche dans une boîte de message.
Si l'utilisateur choisit l'article 'Exit' dans le menu, nous appelons DestroyWindow avec l'handle de notre fenêtre comme paramètre pour qu'il puisse fermer notre fenêtre.
Comme vous pouvez le voir, spécifier le nom du menu dans une classe de fenêtre est très facile. Cependant vous pouvez aussi employer une méthode supplémentaire pour charger un menu dans votre fenêtre. Je ne vous montrerai pas le code source entier ici. Le fichier de ressources est le même pour les deux méthodes. Il y a seulement quelques changements mineurs du fichier source que je vais vous montrer ci-dessous.

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ? ; handle of our menu
Définit une variable de type HMENU pour stocker notre menu handle.

invoke LoadMenu, hInst, OFFSET MenuName
mov hMenu,eax
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,hMenu,\
hInst,NULL

Pour plus d'informations sur LoadMenu, référez-vous à LoadMenu.html

Avant l'appel CreateWindowEx, nous appelons LoadMenu avec l'Instance Handle et un pointeur sur le nom de notre menu. LoadMenu renvoie l'handle de notre menu dans le fichier de ressources que nous passons à CreateWindowEx.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 9: Child Window Controls

Tutorial 9: Child Window Controls

Dans ce tutorial, nous allons voir les 'Child Window Controls' qui sont, pour nos programmes, des mécanismes d'entrée - sortie très importants.

Downloadez le fichier en example ici.

Theorie:

Windows fournit plusieurs classes de fenêtre prédéterminées que nous pouvons aisément employer dans nos propres programmes. La plupart du temps nous les employons, comme par exemple les composants d'une boîte de dialogue. Donc on les appelle généralement les 'Child Window Controls'. Les 'Child Window Controls' traitent leurs propres messages de souris et de clavier et en informent la fenêtre parente quand leurs états ont étés changés. Ils soulagent les programmeurs d'un fardeau énorme, donc vous devez essayer de les employer autant que possible. Dans ce tutorial, je les ai mis sur une fenêtre normale juste pour voir comment vous pouvez les créer et les employer mais en réalité vous devez les mettre dans une boîte de dialogue.
Les exemples de classes de fenêtre prédéterminées sont le bouton, la liste, la boîte à cocher, le bouton radio, d'édition etc.
Pour utiliser un 'Child Window Control', vous devez d'abord le créer avec 'CreateWindow' ou 'CreateWindowEx'. Notez que vous ne devez pas enregistrer la classe de fenêtre puisque Windows l'enregistrera pour vous. Le paramètre 'class name' DOIT ÊTRE le nom de la classe prédéterminée. Quand vous voulez créer un bouton, vous devez mettre "BUTTON" comme nom de classe dans CreateWindowEx. Les autres paramètres que vous devez remplir sont le handle de sa fenêtre parente' et le n°ID pour identifier ce Control. Le n°ID du Control doit être unique parmi les différents Controls. Le n°ID du Control est le numéro qui servira à identifier ce Control. Vous l'employez pour le différencier des autres Controls.
Après que ce Control ait été créé, des messages sont envoyés pour avertir que l'état de la fenêtre parente a changé. Normalement, vous créez les child window grâce au message WM_CREATE de la fenêtre parente. Chaque Control (exemple: les boutons, la zone de saisie de texte=Control d'édition, les barres de défilement en bas et sur le côté des fenêtres,...) est un objet qu'on dépose par dessus la fenêtre principale. Ce sont des objets enfants des la fenêtre dite parente. Il y a donc la fenêtre parente, et ses objets, ses Controls sont ses 'Fenêtres Enfants' (ou Child Window Controls). Une fenêtre enfant (child window) envoie le message WM_COMMAND à sa fenêtre parente, dont son n°ID (celui de ce Control) est dans le mot de poids faible du paramètre wParam, son code dans le mot de poids fort de wParam et son 'Window Handle' (son Handle de Fenêtre (de Control)) dans lParam. Chaque 'Child Window Control' a des codes différents, pour en savoir plus à propos de l'instruction (le code) faîtes appel à votre référence API Win32.
De façon inverse, la fenêtre parente peut également envoyer des messages à ses Controls (fenêtres Enfants), en faisant appel à la fonction SendMessage. La fonction SendMessage envoie donc le message en question en l'accompagnant en plus des valeurs de wParam et lParam vers le child windows ciblée par son 'Window Handle'. C'est une fonction extrêmement utile puisque elle peut envoyer des messages à n'importe quelle fenêtre pourvu que vous sachiez son 'Window Handle' (Window Handle = le n° qui identifie une fenêtre ou bien un objet quelconque comme par exemple un 'champs de saisie de texte' ou bien un 'bouton').
Ainsi, après la création des child windows, la fenêtre parente doit possèder une boucle de traitement de messages pour s'occuper des messages WM_COMMAND pour être capable de recevoir les codes de notification des fenêtres enfant (child windows).

Example:

Nous allons créer une fenêtre qui contient une boîte d'édition (boîte de saisie de texte) et un bouton. Quand vous cliquez sur le bouton, une (MessageBox) boîte de message apparaîtra affichant le texte que vous avez tapé dans la boîte d'édition. Il y a aussi un menu avec 4 Items (4 sous-menu) :
  1. Say Hello -- Met une chaîne de caractères dans la boîte d'édition
  2. Clear Edit Box -- Efface le contenu de la boîte d'édition
  3. Get Text -- Affiche une MessageBox avec le texte de la boîte d'édition
  4. Exit -- Ferme le programme.
.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0
ButtonClassName db "button",0
ButtonText db "My First Button",0
EditClassName db "edit",0
TestString db "Wow! I'm in an edit box now",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndButton HWND ?
hwndEdit HWND ?
buffer db 512 dup(?) ; Buffer pour stocker le texte qu'on inscrit dans la boîte d'édition.

.const
ButtonID equ 1 ; le n°ID du Control de type Button
EditID equ 2 ; le n°ID du Control de type 'Zone d'édition' (de saisie de texte)
IDM_HELLO equ 1
IDM_CLEAR equ 2
IDM_GETTEXT equ 3 ; n°IDs des différents Items contituants le menu
IDM_EXIT equ 4

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName, \
ADDR AppName, WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT, CW_USEDEFAULT,\
300,200,NULL,NULL, hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\
ES_AUTOHSCROLL,\
50,35,200,25,hWnd,8,hInstance,NULL
mov hwndEdit,eax
invoke SetFocus, hwndEdit
invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR ButtonText,\
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
75,70,140,25,hWnd,ButtonID,hInstance,NULL
mov hwndButton,eax
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

On va analyser le programme.
    .ELSEIF uMsg==WM_CREATE
    invoke CreateWindowEx,WS_EX_CLIENTEDGE, \
    ADDR EditClassName,NULL,\
    WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT\
    or ES_AUTOHSCROLL,\
    50,35,200,25,hWnd,EditID,hInstance,NULL
    mov hwndEdit,eax
    invoke SetFocus, hwndEdit
    invoke CreateWindowEx,NULL, ADDR ButtonClassName,\
    ADDR ButtonText,\
    WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
    75,70,140,25,hWnd,ButtonID,hInstance,NULL
    mov hwndButton,eax
Nous créons les différents Controls pendant le traitement du message WM_CREATE. Nous appelons CreateWindowEx avec un style de fenêtre supplémentaire, WS_EX_CLIENTEDGE. Le nom de chaque contrôle est prédéterminé. On a "EDIT" pour le Control de type 'zone d'édition' (ou boîte de saisie de texte), "BUTTON" pour le Control de type 'bouton'. Ensuite nous spécifions le style de chaque 'Child Window' (le style de chacun des objets qui constituent notre fenêtre, de chaque Control. Comme le style du bouton par exemple). Chaque Control possède des styles supplémentaires en plus des styles normaux. Par exemple, le style pour le bouton a le préfixe BS_" pour "Button Style", le style de la zone d'édition a pour préfixe "ES _" pour "Edit Style". Notez que vous mettez un n°ID de Control à la place du Handle du menu. Ceci ne provoquera aucun problème puisque un (Child Window Control) un Control Enfant ne peut pas avoir de menu. (Ex: une fenêtre principale peut avoir un menu mais un simple bouton, sûrment pas).
Après la création de chaque Control, nous obtenons en retour son handle dans une variable pour une future utilisation.
La fonction 'SetFocus' est appelée pour que la boîte d'édition prenne la main (pour que se soit elle qui soit active), comme ça l'utilisateur pourra taper son texte tout de suite après l'appel de cette fonction.
Vient maintenant la partie vraiment passionnante. Chaque 'Child Window Control' envoie ses notifications (Messages allant d'un 'Child Control' vers sa 'Fenêtre Parente', le contraire c'est un Message simplement) à sa 'Parent Window' (sa fenêtre parente) avec WM_COMMAND.

.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0

Rappelez-vous bien qu'un menu envoie lui aussi des messages WM_COMMAND vers la fenêtre parente pour l'informer de son propre état. Comment pouvez-vous faire la différence entre des messages WM_COMMAND, émis par un menu (Child Window) ou par un contrôle (effectué par la Parent Window) ? Voici ci-dessous la réponse :

 | Mot de poids faible de wParam | Mot de poids fort de wParam | lParam
Menu | Menu ID | 0 | 0
Control | Control ID | Notification code | Child Window Handle

Vous pouvez voir que vous devez vérifier lParam. Si c'est zéro, l'actuel message WM_COMMAND provient d'un menu. Vous ne pouvez pas employer wParam pour faire la différence entre un menu et un Control, puisque le 'menu ID' et 'control ID' peuvent être identiques et que le code de notification peut être zéro.

.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK

Vous pouvez écrire une chaîne de caractère (du texte) dans la boîte d'édition en appelant la fonction SetWindowText. Vous pouvez aussi effacez le contenu de cette boîte d'édition en appelant SetWindowText avec le NULL. SetWindowText est une fonction API d'usage flexible. Vous pouvez employer SetWindowText pour modifier du titre dans une fenêtre ou pour changer le texte inscrit sur un bouton.
Pour importer du texte dans une boîte d'édition, vous employez GetWindowText. (Comprenez seulement que, dans ce cas on écrit pas manuellement le texte dans la boîte d'édition (de saisie), mais on le fait s'inscrire dans la zone de saisie comme on aurait pu faire s'inscrire un texte dans une MessageBox).

.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF

Ce petit bout de code réagit à la seule condition que l'utilisateur presse le bouton. D'abord, il vérifie le mot bas (ou de poids faible) de wParam pour voir si le 'control ID' (le n°ID du Control) correspond à celui du bouton. S'il l'est, il vérifie le haut mot (ou de poids fort) de wParam pour savoir si on a bien l'indication BN_CLICKED, qui est envoyée quand le bouton est cliqué.
La partie qui est intéressante, c'est après qu'on soit certain que le code de notification soit BN_CLICKED. Nous voulons récupérer le texte qu'on vient d'écrire dans la boîte d'édition et le montrer dans une boîte de message (une MessageBox). Nous pourrions faire un double du code de la section IDM_GETTEXT ci-dessus mais ça n'a pas de sens. Si d'une façon ou d'une autre nous pouvions envoyer un message WM_COMMAND avec le mot bas de wParam, alors la valeur contenue dans IDM_GETTEXT serait transmise à notre propre procédure de fenêtre, nous pouvons donc éviter la duplication de ce code et simplifier notre programme. La solution, c'est la fonction SendMessage. Cette fonction envoie n'importe quel message à n'importe quelle fenêtre avec n'importe quel wParam et lParam. Ainsi au lieu de recopier du code, nous appelons SendMessage avec :
1- l'Handle de la fenêtre parente.
2- WM_COMMAND.
3- IDM_GETTEXT.
4- 0.
Ceci a le même effet que de sélectionner le menu Item "Get Text" (un de nos sous-menu) du menu. La procédure de fenêtre ne voit aucune différence entre les deux. (La procédure de fenêtre = la procédure qui s'occupe du fonctionnement de notre fenêtre, comme la détection d'un appui sur un bouton ou bien une inscription de texte dans une zone de saisie)
Vous devez essayer d'employer cette technique le plus possible pour organiser votre code au mieux.
Dernier point mais non le moindre, n'oubliez pas d'utiliser la fonction TranslateMessage à l'intérieur de la boucle de message. Puisque vous devez taper du texte dans la boîte d'édition, votre programme doit traduire des entrées clavier brutes en un texte lisible. Si vous omettez cette fonction, vous serez incapable de taper quoique ce soit dans votre boîte d'édition.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 10: Boîte de Dialogue comme Fenêtre Principale

Tutorial 10: Boîte de Dialogue
Comme Fenêtre Principale

Vient maintenant un objet vraiment intéressante du GUI (NdT : Graphic User Interface), à savoir : la boîte de dialogue. Dans ce Tutorial (et le suivant), nous apprendrons comment se servir d'une boîte de dialogue comme fenêtre principale.

Downloadez le premier exemple ici, le second Là.

Théorie:

Si vous avez joué assez longtemps avec les exemples des Tutorials précédants, vous avez dû découvrir que la touche TAB ne permettait pas de changer de centre d'entrée d'un 'Child Window Control' à un autre (EX : si vous avez un bouton et une zone de saisie, la touche TAB sert à activer puis à passer de l'un à l'autre. Essayez sur les exemples de ce TUT) . Dans les Tutorials précédents, le seul moyen d'activer le centre d'entrée d'un des 'Child Wondow Control' était de simple-cliquer sur l'un d'eux. C'est une situation un peu lourde. Une nouvelle chose à remarquer, c'est que j'ai changé la couleur du fond de la fenêtre parente, elle est grise maintenant au lieu de blanche comme dans des exemples précédents. C'est fait pour que la couleur des 'child window controls' puisse se mélanger sans différence avec la couleur du 'secteur client' de la fenêtre parente. Il y a une autre façon de contourner ce problème mais ce n'est pas facile. Vous devez sous-classer tous les 'child window controls' de votre fenêtre parente.
La raison pour laquelle un tel inconvénient existe, c'est qu'à l'origine les 'Child Window Controls' ont étés conçus pour travailler avec une boîte de dialogue, et non pas une simple fenêtre. La couleur par défaut des 'Child Window Controls' tel qu'un bouton est grise parce que le 'secteur client' d'une boîte de dialogue est normalement gris. Donc ils se marient l'un avec l'autre sans que le programmeur ne se casse encore plus la tête sur un détail comme ça.
Avant que nous n'allions plus profondément dans les détails, nous devons d'abord comprendre ce qu'est une boîte de dialogue. Une boîte de dialogue n'est rien de plus d'une fenêtre normale qui est conçue pour travailler avec des'child window controls'. Windows possède aussi son propre ''administrateur de boîte de dialog'' lequel est responsable de la majeure partie de la logique du clavier, comme par exemple le changement du centre d'entrée quand l'utilisateur presse 'TAB' (= activer un autre Child Window Control avec TAB), ou bien en 'Validant' la touche dont son centre d'entrée est sélectionné si la touche ENTER est pressée (=quand vous avez 2 boutons dans une fenêtre, un OK l'autre NON, si c'est OK qui est sélectionné en cadre pointillé, en appuyant sur ENTER c'est comme si on avait cliqué sur OK ), etc… donc avec ça, les programmeurs peuvent traiter des tâches de plus haut niveau. Les boîtes de dialogue sont principalement employées comme des dispositifs d'Entrée / Sorties. Puisqu'une telle boîte de dialogue peut être considérée comme une "boîte noire" d' Entrée / Sortie, vous n'avez pas besoin de savoir comment travaille intérieurement cette boîte de dialogue pour pouvoir l'employer. Vous devez seulement savoir comment elle agir. C'est le principe d'OBJET ORIENTÉ PROGRAMMÉ (OOP) qui fait en sorte que l'information est dissimulée (tout n'est pas apparent dans son fonctionnement). Si la boîte noire était *parfaitement* conçue, l'utilisateur devrait pouvoir s'en servir sans aucune connaissance de son fonctionne. Le hic c'est que la boîte noire doit être parfaite, et en réalité c'est difficile à réaliser car les API Win32 sont aussi conçues comme des boîtes noire.
Bien, il semble que nous dévions de notre chemin. On va revenir à notre sujet. Les boîtes de dialogue sont conçues pour réduire le travail du programmeur. Normalement si vous utilisez des 'Child Window Controls' sur une fenêtre normale, vous devez les sous-classer et écrire (définir) la logique du clavier vous-même. Mais si vous les mettez sur une boîte de dialogue, tout ceci sera manipulé pour vous. Vous devez seulement savoir comment s'utilisent vos Child Window dans la boîte de dialogue ou comment envoyer des commandes.
Une boîte de dialogue est définie comme une ressource (un objet qu'on défini pour pouvoir l'utiliser parmi d'autres) un peu de la même façon qu'un menu. Vous décrivez les caractéristiques de votre boîte de dialogue et de ses commandes, puis ensuite vous compilez le scénario de ressources avec un éditeur de ressources.
Remarquez que toutes les ressources sont réunies dans le même fichier de scénario de ressource. Vous pouvez employer n'importe quel éditeur de texte pour écrire un calibre de boîte de dialogue mais je ne le recommande pas. Vous vaut mieux employer un éditeur de ressources pour faire le travail visuellement puisque l'arrangement des 'child window controls' sur une boîte de dialogue est difficile à faire manuellement. Plusieurs éditeurs de ressources excellents sont disponibles. La plupart des Packs de compilateurs incluent aussi leurs propres éditeurs de ressources. Vous pouvez les employer pour créer un scénario de ressources pour votre programme et couper ensuite les lignes sans rapport comme celles liés à MFC.
Il y a deux principaux types de boîte de dialogue : 'modale' et 'sans modèle'. Une boîte de dialogue sans modèle vous laisse changer son centre d'entrée pour une autre fenêtre. Le meilleur exemple c'est la boîte de dialogue 'RECHERCHER' de WORD. Il y a deux sous-types de boîte de dialogue modale : l'application modale et le système modal. Une boîte de dialogue d'application modale ne vous laisse pas changer son centre d'entrée pour aller vers une autre fenêtre dans la même application. Par contre vous pouvez changer le centre d'entrée de la fenêtre pour aller vers d'AUTRES applications. Le système de la boîte de dialogue modale ne vous permet pas de changer son centre d'entrée pour une autre fenêtre avant que vous ne répondiez d'abord.
On créé une 'boîte de dialogue sans modèle' en appelant la fonction API CreateDialogParam. On créé une boîte de dialogue modale en appelant DialogBoxParam. La seule différence entre une boîte de dialogue d'application modale et un système modal est le style DS_SYSMODAL. Si vous incluez le style DS_SYSMODAL dans un calibre de boîte de dialogue, cette boîte de dialogue sera un système modale.
Vous pouvez communiquer avec n'importe quel 'child window control' dans une boîte de dialogue en employant la fonction SendDlgItemMessage. Sa syntaxe est la suivante :
SendDlgItemMessage proto hwndDlg:DWORD,\
idControl:DWORD,\
uMsg:DWORD,\
wParam:DWORD,\
lParam:DWORD
Cet appel d'API est super utile pour l'action réciproque avec les 'child window control'. Par exemple, si vous voulez récupérer le texte d'un contrôle d'édition, vous pouvez faire ceci :
call SendDlgItemMessage, hDlg, ID_EDITBOX, WM_GETTEXT, 256, ADDR text_buffer
Pour savoir quel message (quelle commande) vous devez utiliser, vous devez consulter votre référence Win32 API.
Windows fournit aussi plusieurs fonctions API de contrôle-spécifiques pour récupérer et transmettre des données rapidement, par exemple, GetDlgItemText, CheckDlgButton etc. Ces fonctions de contrôle-spécifiques sont mis à la disposition du programmeur. Donc il ne doit pas rechercher les significations de wParam et lParam pour chaque message. Normalement, vous devez utiliser les appels d'API de contrôle-spécifiques quand ils sont disponibles puisque ils servent à vous faciliter l'écriture de votre code source. On recours seulement à SendDlgItemMessage si aucun des appels d'API de contrôle-spécifiques n'est disponible.
L'administrateur de boîte de dialogue de Windows envoie quelques messages à une fonction de rappel de service spécialisée, laquelle est appelée 'procédure de boîte de dialogue' et qui a le format suivant :
DlgProc proto hDlg:DWORD ,\
iMsg:DWORD ,\
wParam:DWORD ,\
lParam:DWORD
La 'procédure de boîte de dialogue' est très semblable à une 'procédure de fenêtre' à part le type de valeur de retour qui est TRUE / FAULSE au lieu de LRESULT. L'administrateur de boîte de dialogue interne à Windows EST la vraie procédure de fenêtre pour la boîte de dialogue. Il appelle notre procédure de boîte de dialogue avec les messages qu'il a reçu. Donc la règle générale en vigueur consiste en ce que : si notre procédure de boîte de dialogue traite un message, il DOIT retourner la valeur TRUE (VRAIE) dans eax et s'il ne traite pas le message, il doit retourner FAULSE (FAUX) dans eax. Remarquez qu'une procédure de boîte de dialogue ne transmet pas les messages et elle ne traite pas l'appel de DefWindowProc puisque ce n'est pas une procédure de fenêtre réelle (normale).
Il y a deux utilisations distinctes d'une boîte de dialogue. Vous pouvez l'employer en tant que fenêtre principale de votre application ou l'employer comme un dispositif d'entrée. Nous allons examiner la première approche dans ce tutorial.
""L'utilisation d'une boîte de dialogue comme fenêtre principale" peut être interprétée dans deux sens différents.
  1. Vous pouvez calibrer une boîte de dialogue comme un calibre de classe que vous déclarez avec l'appel de RegisterClassEx. Dans ce cas, la boîte de dialogue se comporte comme une fenêtre "normale" : elle reçoit des messages via une procédure de fenêtre mentionnée par le membre lpfnWndProc de la classe de fenêtre, et non pas par une procédure de boîte de dialogue. L'avantage de cette approche est que vous n'avez pas à créer vous-même de 'child window controls'. Windows les crée pour vous lorsque la boîte de dialogue est créée. Ainsi Windows gère la logique du clavier pour vous. De plus vous pouvez choisir le curseur et l'icône de votre fenêtre dans la structure de classe de fenêtre.

    2. Votre programme crée seulement une boîte de dialogue sans créer aucune fenêtre parente. Cette approche fait une boucle de message inutile puisque les messages sont envoyés directement à la procédure de boîte de dialogue. Vous n'avez même pas besoin d'enregistrer une classe de fenêtre!
Ce Tutorial va être long. Je présenterai la première approche suivie par le deuxième.

Exemples:

dialog.asm

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "DLGCLASS",0
MenuName db "MyMenu",0
DlgName db "MyDialog",0
AppName db "Our First Dialog Box",0
TestString db "Wow! Je suis dans une boîte d'édition maintenant",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)

.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hDlg:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,DLGWINDOWEXTRA
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
mov hDlg,eax
invoke ShowWindow, hDlg,SW_SHOWNORMAL
invoke UpdateWindow, hDlg
invoke GetDlgItem,hDlg,IDC_EDIT
invoke SetFocus,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.IF dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Dialog.rc

#include "resource.h"

#define IDC_EDIT 3000
#define IDC_BUTTON 3001
#define IDC_EXIT 3002

#define IDM_GETTEXT 32000
#define IDM_CLEAR 32001
#define IDM_EXIT 32003

MyDialog DIALOG 10, 10, 205, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "Our First Dialog Box"
CLASS "DLGCLASS"
BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Dis Bonjour", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13, WS_GROUP
END

MyMenu MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END

Analyse:

On va analyser ce premier exemple.
Cet exemple montre comment créer un calibre de dialogue comme une classe de fenêtre, et créer "une fenêtre" avec cette classe. ça simplifie notre programme puisque on n'a pas besoin de créer de 'child window controls' soi-même.
On va d'abord analyser le calibre de dialogue.

MyDialog DIALOG 10, 10, 205, 60

On déclare le nom d'un dialogue, dans ce cas, "MyDialog" suivi du mot-clé "DIALOG". Les quatre nombres suivants sont : x, y, largeur et hauteur de la boîte de dialogue, ce sont des unités propres aux DialogBox (C'est pas des pixels).

STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK

Déclare les styles de la boîte de dialogue.

CAPTION "Our First Dialog Box"

C'est le texte qui apparaîtra dans la barre de titre de la boîte de dialogue.

CLASS "DLGCLASS"

Cette ligne est cruciale. C'est ce mot-clé CLASS qui nous permet d'employer le calibre de boîte de dialogue comme une 'Window Class' (classe de fenêtre). Puis, suivant le mot-clé on trouve le nom de la "Window Class"

BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Dis Bonjour", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END

Le susdit bloc définit les 'child window controls' de la boîte de dialogue. Ils sont définis entre les mots-clés BEGIN et END. Généralement la syntaxe est la suivante :

    control-type "text" ,controlID, x, y, width, height [,styles]
Les 'types' de contrôle sont propre au compilateur de ressource, donc vous devez consulter le manuel.
Maintenant nous voyons le code de source assembleur. La partie intéressante est dans la structure de la 'Window Class ' :
    mov wc.cbWndExtra,DLGWINDOWEXTRA
    mov wc.lpszClassName,OFFSET ClassName
Normalement, ce membre est le NULL, mais si nous voulons enregistrer un calibre de boîte de dialogue comme une Window Class (classe de fenêtre), nous devons mettre ce membre à la valeur DLGWINDOWEXTRA. Notez que le nom de la classe doit être identique à celui qui est après le mot-clé CLASS pour le calibre de boîte de dialogue. Les membres restants sont initialisés comme d'habitude. Après que vous ayez remplis la structure de classe de fenêtre, déclarez-la avec RegisterClassEx. Ça vous semble familier ? C'est la même routine que pour enregistrer une classe de fenêtre normale.
    invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
Après avoir définit notre "Window Class", nous créons notre boîte de dialogue. Dans cet exemple, je la crée comme une boîte de dialogue sans modèle grâce à la fonction CreateDialogParam. Cette fonction prend 5 paramètres, mais en réalité, vous n'avez besoin que des deux premier : l'instance handle et le pointeur qui justement pointe sur 'le nom du calibre de DialogBox'. Notez que le 2ème paramètre n'est pas un pointeur sur le nom de class.
À ce point, la boîte de dialogue et ses 'child window controls' sont créés par Windows. Votre procédure de fenêtre recevra comme d'habitude le message WM_CREATE.
    invoke GetDlgItem,hDlg,IDC_EDIT
    invoke SetFocus,eax
Maintenant que la boîte de dialogue est créée, j'aimerai mettre le centre d'entrée au contrôle d'édition (= on veut que la zone de saisie de texte soit active). Si je met ces codes dans la section WM_CREATE, l'appel GetDlgItem échouera jusqu'à ce que les 'child window controls' ne soient créés. La seule façon pour faire cela est de l'appeler après que la boîte de dialogue et tous ses 'child window controls' soient créés. C'est pour ça que j'ai mis ces deux lignes après l'appel à UpdateWindow. la fonction GetDlgItem obtient le contrôle ID et renvoie le contrôle du 'Window Handle' associé. C'est de cette manière que vous pouvez récupérer un 'Window Handle' lorsque vous connaissez son 'contrôle ID'.

invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF

Le programme entre dans la boucle de message et avant que nous traduisions et expédiions des messages, nous appelons la fonction IsDialogMessage pour que l'administrateur de boîte de dialogue puisse s'occuper de la logique du clavier à notre place. Si ces retours de fonction sont TRUES(VRAIS), alors ça signifie que le message est destiné à la boîte de dialogue et est traité par l'administrateur de la boîte de dialogue. Remarquez une autre différence par rapport Tutorial précédent. Quand la procédure de fenêtre veut récupérer le texte qui est inscrit dans la zone d'édition, elle appelle la fonction GetDlgItemText au lieu de GetWindowText. GetDlgItemText accepte un contrôle ID au lieu d'un 'Window Handle'. Ceci rend l'appel plus facile dans le cas vous employez une boîte de dialogue.

Maintenant on va voir à la deuxième approche, à savoir : l'utilisation d'une boîte de dialogue comme fenêtre principale. Dans l'exemple suivant, je vais créer une boîte de dialogue d'application modale. Vous ne trouverez pas de boucle de message ou de procédure de fenêtre parce qu'elles ne sont pas nécessaires!
dialog.asm (part 2)

.386
.model flat,stdcall
option casemap:none

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
DlgName db "MyDialog",0
AppName db "Our Second Dialog Box",0
TestString db "Wow! Je suis dans une zone d'édition",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)

.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
invoke ExitProcess,eax

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSEIF ax==IDM_EXIT
invoke EndDialog, hWnd,NULL
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.if dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
mov eax,FALSE
ret
.ENDIF
mov eax,TRUE
ret
DlgProc endp
end start

dialog.rc (partie 2)

#include "resource.h"

#define IDC_EDIT 3000
#define IDC_BUTTON 3001
#define IDC_EXIT 3002

#define IDR_MENU1 3003

#define IDM_GETTEXT 32000
#define IDM_CLEAR 32001
#define IDM_EXIT 32003

MyDialog DIALOG 10, 10, 205, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "Our Second Dialog Box"
MENU IDR_MENU1
BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Dis Bonjour", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END

IDR_MENU1 MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END

The analysis follows:

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD

Nous déclarons le prototype de fonction de DlgProc ,ainsi nous pouvons lui attribuer l'opérateur addr à la ligne ci-dessous :

invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL

la susdite ligne appelle la fonction DialogBoxParam qui prend 5 paramètres : l'Instance handle', le 'nom du calibre de boîte de dialogue', l'handle de la fenêtre parente', l'adresse de la procédure de la de boîte de dialogue et des données dialogue-spécifiques. DialogBoxParam crée une boîte de dialogue modale. Rien ne sera retourné tant que la boîte de dialogue ne sera pas détruite.

.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0

La procédure de boîte de dialogue ressemble à une procédure de fenêtre sauf qu'elle ne reçoit pas le message WM_CREATE. Le premier message qu'elle reçoit est WM_INITDIALOG. Normalement vous pouvez mettre le code d'initialisation ici. Remarquez que vous devez renvoier la valeur TRUE (VRAIE) dans eax si vous traitez le message.
L'administrateur interne de boîte de dialogue n'envoie pas à notre procédure de boîte de dialogue par défaut le message WM_DESTROY quand WM_CLOSE est envoyé à notre boîte de dialogue. Ainsi si nous voulons réagir quand l'utilisateur presse le bouton proche sur notre boîte de dialogue, nous devons traiter le message WM_CLOSE. Dans notre exemple, nous envoyons le message WM_COMMAND avec la valeur IDM_EXIT dans wParam. Ça a le même effet que lorsque l'utilisateur choisit l'article de menu de Sortie (le sous-menu : EXIT). EndDialog est appelé dans la réponse à IDM_EXIT.
Le traitement du messages WM_COMMAND reste le même.
Quand vous voulez détruire la boîte de dialogue, il faut appeler la fonction EndDialog. N'essayez pas DestroyWindow! EndDialog ne détruit pas la boîte de dialogue immédiatement. Il met seulement un drapeau(Flag) pour l'administrateur de la boîte de dialogue et continue à exécuter les instructions suivantes.
Maintenant on va examiner le fichier de ressources. Un changement notable est qu'au lieu de l'utilisation d'une chaîne de texte pour le nom de menu nous employons une valeur, IDR_MENU1. C'est nécessaire si vous voulez attacher un menu à une boîte de dialogue créée avec DialogBoxParam. Notez que dans le calibre de boîte de dialogue, vous devez ajouter le mot-clé MENU suivi de la ressource du menu ID.
La différence entre les deux exemples de ce Tutorial est le manque d'une icône dans le dernier exemple. Cependant, vous pouvez mettre l'icône en envoyant le message WM_SETICON à la boîte de dialogue pendant WM_INITDIALOG.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 11: More about Dialog Box

Tutorial 11: Encore Plus à Propos
Des Boîtes de Dialogue

Dans ce Tutorial, nous allons en apprendre encore plus sur la boîte de dialogue. Plus spécialement, nous allons voir comment employer les boîtes de dialogue en tant que dispositifs d'entrée-sortie. Si vous avez lu le tutorial précédent, celui-ci ne sera qu'une brise. Il s'agit ici d'une modification mineure pour pouvoir utiliser les boîtes de dialogue comme des adjonctions à notre fenêtre principale. Aussi dans ce tutorial, nous allons apprendre à utiliser les boîtes de dialogue comme elles sont utilisées normalement.

Downloadez les exemples de DialogBox ici et ici. Downloadez l'exemple de DialogBox couramment utilisée ici.

Théorie:

Vite fait, voici ce qui doit être dit à propos de l'emploi des boîtes de dialogue en tant que dispositifs d'entrée-sortie de notre programme. Comme d'habitude, notre programme crée la fenêtre principale puis quand vous voulez montrer la DialogBox, appelez juste CreateDialogParam ou bien DialogBoxParam. Avec l'appel de DialogBoxParam, vous n'avez rien de plus à faire, traitez seulement les messages dans la procédure de la boîte de dialogue. Avec CreateDialogParam, vous devez insérer un 'Call IsDialogMessage' dans la boucle de message pour permettre à l'administrateur de la boîte de dialogue de gérer la navigation du clavier dans votre boîte de dialogue à votre place. Puisque ces deux cas sont rares, je ne mettrai pas le code source ici. Vous pouvez télécharger les exemples et les examiner vous-même, ici et ici.
Il existe des objets prédéfinits et tout près à être utilisés, ce sont les 'Common Controls' voir le Tutorial 18. Il en existe beaucoup, mais ici nous nous limiterons seulement aux objets de type : DialogBox.
Continuons avec ces 'Common' boîtes de dialogue (DialogBox prédéfinies). Windows possède des DialogBox prédéterminées pour que vos applications puissent les utiliser. Ces boîtes de dialogue existent pour servir d'interfaces utilisateur standardisées. Il s'agit de boîtes comme celle de l'imprimante, la couleur, la fonte ou les boîtes de dialogue de recherche...etc. Vous devez les utiliser dès que c'est possible. Ces boîtes de dialogue prédéfinies résident dans comdlg32.dll. Pour les employer, vous devez vous lier avec comdlg32.lib. Vous créez ces boîtes de dialogue en appelant les fonctions appropriées dans la bibliothèque des 'Common' DialogBox . Pour sélectionner et ouvrir des fichiers, on se sert d'une boite de dialogue déjà à notre disposition, c'est la spéciale boîte d'ouverture de fichier, la boîte GetOpenFileName. Pour sauvegarder des fichier on se sert de sa soeur la DialogBox GetSaveFileName, pour les impressions c'est la boîte de dialogue de l'imprimante, qui est PrintDlg et cetera. Chacune de ces fonctions utilise un pointeur comme paramètre dans sa structure. Vous devez les regarder dans la référence Win32 API. Dans ce Tutorial, on va justement voir comment créer et employer cette DialogBox servant à ouvrir des fichiers.
Voici ci-dessous le prototype de fonction de GetOpenFileName :
GetOpenFileName proto lpofn:DWORD
Vous pouvez voir qu'il ne reçoit qu'un seul paramètre, un pointeur sur la structure OPENFILENAME. La valeur de retour TRUE(VRAI) signifie que l'utilisateur vient de choisi un fichier pour l'ouvrir, sinon la valeur en retour est FALSE(FAUX). Voyons la structure OPENFILENAME maintenant.
OPENFILENAME STRUCT
lStructSize DWORD ?
hwndOwner HWND ?
hInstance HINSTANCE ?
lpstrFilter LPCSTR ?
lpstrCustomFilter LPSTR ?
nMaxCustFilter DWORD ?
nFilterIndex DWORD ?
lpstrFile LPSTR ?
nMaxFile DWORD ?
lpstrFileTitle LPSTR ?
nMaxFileTitle DWORD ?
lpstrInitialDir LPCSTR ?
lpstrTitle LPCSTR ?
Flags DWORD ?
nFileOffset WORD ?
nFileExtension WORD ?
lpstrDefExt LPCSTR ?
lCustData LPARAM ?
lpfnHook DWORD ?
lpTemplateName LPCSTR ?
OPENFILENAME ENDS
On va voir la signification des membres fréquemment employés.
lStructSize | Taille de la structure OPENFILENAME, en octets.
hwndOwner | handle de la boîte de dialogue qui sert à ouvrir des fichiers.
hInstance | Instance Handle de l'application qui crée la 'boîte de dialogue qui sert à ouvrir des fichiers'.
lpstrFilter | Sert à filtrer le type de chaînes de caractères à ouvrir. Il faut 2 NULL pour terminer cette instruction. La première chaîne de caractères est la description (donc le texte). La deuxième c'est le type de filtre. Par exemple, dans un répertoire on peut sélectionner tous les dossiers ou bien que les .exe ou bien seulement les .zip……etc: FilterString db "All Files (*.*)",0, "*.*",0 db "Text Files (*.txt)",0,"*.txt",0,0 Remarquez bien que c'est la deuxième chaîne de caractères (pour chaque ligne) qui en réalité sert à Windows à filtrer les fichiers. Donc vous êtes obligés de mettre un 0 supplémentaire à la fin de la chaîne de caractères du filtre pour en indiquer sa fin.
nFilterIndex | Indique quelle [paire de chaîne de caractères de] filtre sera utilisée par défaut dans la DialogBox qui sert à ouvrir les fichiers. Ce sera le 1, pour la première paire "All Files (*.*)", le 2 pour la deuxième paire "Text Files (*.txt)" etc. Ainsi dans notre exemple, si nous spécifions nFilterIndex en tant que 2, le deuxième modèle, "*.txt" sera employé.
lpstrFile | Le pointeur du buffer qui contient le nom de fichier qu'on a l'habitude d'initialiser dans la boîte d'édition de notre DialogBox (ici c'est le pointeur qui pointe sur le texte " ALL FILES ". Le pointeur doit avoir une longueur d'au moins 260 octets. Après que l'utilisateur ait choisit un fichier pour l'ouvrir, le nom de ce fichier ainsi que le chemin (Patch) complète où il est stocké, sont gardés dans ce buffer pstrFile. Vous pourrez en extraire l'information plus tard.
nMaxFile | La taille de buffer lpstrFile.
lpstrTitle | Pointeur du titre de la DialogBox qui sert à ouvrir les fichiers. (donc c'est le titre).
Flags | Détermine le style et les caractéristiques de la boîte de dialogue.
nFileOffset | Après que l'utilisateur ait choisi un fichier pour l'ouvrir, ce membre contient le n° du premier caractère du nom du fichier réel. Par exemple, si le nom complète avec son chemin est "c:\Windows\system\lz32.dll", ce membre contiendra la valeur 18.
nFileExtension | Après que l'utilisateur ait choisi un fichier pour l'ouvrir, ce membre contient le numéro du premier caractère de l'extension du fichier. Ici c'est 23

Exemple:

Le programme suivant montre une boîte de dialogue servant à ouvrir des fichiers. Pour en ouvrir un, l'utilisateur doit choisir le Fichier avec le sous-menu 'OPEN'. Quand l'utilisateur choisit un fichier dans la DialogBox, le programme affiche une MessageBox montrant le nom complet, à savoir le nom du fichier avec l'extension de ce fichier.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

.const
IDM_OPEN equ 1
IDM_EXIT equ 2
MAXSIZE equ 260
OUTPUTSIZE equ 512

.data
ClassName db "SimpleWinClass",0
AppName db "Our Main Window",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
OurTitle db "-=Our First Open File Dialog Box=-: Choose the file to open",0
FullPathName db "The Full Filename with Path is: ",0
FullName db "The Filename is: ",0
ExtensionName db "The Extension is: ",0
OutputString db OUTPUTSIZE dup(0)
CrLf db 0Dh,0Ah,0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if ax==IDM_OPEN
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hwndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
mov ofn.lpstrTitle, OFFSET OurTitle
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke lstrcat,offset OutputString,OFFSET FullPathName
invoke lstrcat,offset OutputString,ofn.lpstrFile
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset FullName
mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileOffset
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset ExtensionName
mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileExtension
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax
invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK
invoke RtlZeroMemory,offset OutputString,OUTPUTSIZE
.endif
.else
invoke DestroyWindow, hWnd
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hwndOwner
push hInstance
pop ofn.hInstance

Nous remplissons notre structure OPENFILENAME (que nous avons ici appelée 'ofn') avec les membres courants (de tout programme). Exemple 2e et 3e ligne: on place temporairement le Handle de notre programme sur la pile pour ensuite le reprendre. Et c'est le membre hwndOwner de notre structure 'ofn' qui le reçoit.

mov ofn.lpstrFilter, OFFSET FilterString

Ce FilterString est le filtre du nom de fichier que nous définissons comme suit :

FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
Remarquez que les quatre chaînes de caractères sont terminées par un Zéro. La première chaîne est la description de la chaîne suivante (EX : ''ALL FILES est la description de *.*''). Le modèle réel est la 2e chaîne de caractères, dans notre cas ce sont *. * et *.txt. En réalité nous pouvons prendre n'importe quel modèle d'extension. exemple : *.wavwav . Nous DEVONS mettre un zéro supplémentaire après la dernière chaîne de caractères pour à montrer que c'est la fin de la chaîne du filtre. N'oubliez surtout pas ça, sinon votre DialogBox se comportera étrangement.

mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE

Nous indiquons où la boîte de dialogue mettra le nom du fichier que l'utilisateur vient de choisir. Notez que nous devons spécifier sa taille dans le membre nMaxFile. Plus tard, nous pourrons extraire le nom du fichier contenu dans ce buffer.

mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY

Flags indique les caractéristiques de la boîte de dialogue.
Les 'flags' (les drapeaux) OFN_FILEMUSTEXIST et OFN_PATHMUSTEXIST exigent que le nom du fichier et le chemin que l'utilisateur tape dans la zone d'édition DOIVENT exister.
le 'flag' OFN_LONGNAMES dit à la boîte de dialogue de montrer des noms de fichier longs. (et non pas des trucs du genre 'nomtropl~1.exe')
Le flag OFN_EXPLORER indique que l'apparition de la boîte de dialogue doit être semblable à celle de l'Explorer Windows.
Le flag OFN_HIDEREADONLY cache la 'case à cocher : read-only ' de la boîte de dialogue.
Il y a encore beaucoup d'autres flags que vous pouvez employer. Consultez votre référence Win32 API.

mov ofn.lpstrTitle, OFFSET OurTitle

Ce sera le titre de votre boîte de dialogue qui sert à ouvrir des fichiers. Si vous effacez totalement cette ligne, son titre par défaut sera 'Ouverture'

invoke GetOpenFileName, ADDR ofn

Appelle la fonction GetOpenFileName. On passage le pointer de la structure 'ofn' en tant que paramètre pour cette fonction.
À partir d'ici, la boîte de dialogue qui ouvre les fichiers, est affichée à l'écran. La fonction ne retournera pas avant que l'utilisateur ne choisisse un fichier pour l'ouvrir ou bien qu'il ne presse le bouton ANNULER ou FERMER de la boîte de dialogue.
Il renverra la valeur TRUE(VRAI) dans eax si l'utilisateur choisit un fichier pour l'ouvrir. Sinon, il retournera FALSE(FAUX).

.if eax==TRUE
invoke lstrcat,offset OutputString,OFFSET FullPathName
invoke lstrcat,offset OutputString,ofn.lpstrFile
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset FullName

Dans le cas où l'utilisateur choisit un fichier pour l'ouvrir, nous préparons une chaîne de caractère pour qu'elle soit affichée dans une MessageBox. Nous réservons un bloc de mémoire dans la variable OutputString puis ensuite nous employons la fonction API, lstrcat, pour enchaîner ensembles les chaîne de caractères . Pour faire apparaître les chaînes sur plusieurs lignes, nous devons séparer chaque ligne avec un retour chariot.

mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileOffset
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax

Les susdites lignes exigent quelques explications. nFileOffset contient le n° dans l'ofn.lpstrFile (ce n° c'est son positionnement). Mais vous ne pouvez pas les ajouter ensemble directement puisque nFileOffset est une variable de TAILLE 'WORD' et lpstrFile est de TAILLE 'DWORD'. Il faut donc mettre la valeur de nFileOffset dans le mot bas (le mot de poids faible) de ebx et l'ajouter à la valeur de lpstrFile.

invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK

Nous affichons la chaîne de caractères dans la MessageBox (boîte de message).

invoke RtlZerolMemory,offset OutputString,OUTPUTSIZE

Nous devons *effacer* l'OutputString avant que nous ne puissions remplir une autre chaîne de caractères. Donc nous employons la fonction RtlZeroMemory pour faire le travail.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 12: Memory Management and File I/O

Tutorial 12 : Gestion de la Mémoire
Et des Fichiers d'entrée-sortie

Dans ce Tutorial, nous allons étudier de façon rudimentaire la gestion de la mémoire et les opérations sur les fichiers i/o (i/o=input/output=entrée sortie). En plus nous utiliserons le Common Control de type 'boîtes de dialogue d'overture de fichiers' (OpenFileName) en tant que dispositifs d'entrée-sortie.

Downloadez l'exemple ici.

Théorie:

La gestion de la mémoire sous Win32 du point de vue de l'application est très simple et directe. Chaque process possède 4 GB d'espace d'adresses mémoire. Le modèle de mémoire employé, est appelé le modèle de mémoire 'FLAT'(plat). Dans ce modèle, tous les registres de segment pointent sur la même adresse de départ, un offset sur 32 bits. Ainsi, une application peut accéder à n'importe quel endroit de la mémoire dans son propre espace d'adresses sans avoir besoin de changer la valeur des registres. Ça simplifie beaucoup la gestion de la mémoire. Il n'y a plus de pointers ou registres "Near" ou "FAR" désormais (pointers proches ou lointains).
Sous Win16, il y a deux principales sortes de fonctions API de mémoire : Global et Local. Le type d'API Global peut utiliser des Call qui vont vers des parties de mémoire se trouvant dans d'autres segments. Ainsi ce type d'API Global sont des fonctions de mémoires "lointaines". Le type d'API Local utilise des Call qui traitent uniquement avec la partie locale où le process en est . Donc ce sont des fonctions de mémoire "proches". Sous Win32, ces deux types sont identiques. Si vous appelez GlobalAlloc ou LocalAlloc, vous obtenez exactement le même résultat.
Voici les étapes pour allouer et utiliser la mémoire :
  1. Réservez un bloc de mémoire en appelant GlobalAlloc. Cette fonction renvoie l'handle du bloc de mémoire demandé.
  2. "Lockez(Fermez)" le bloc de mémoire en appelant GlobalLock. Cette fonction reçoit l'handle du bloc de mémoire et renvoie le pointer du bloc de mémoire.
  3. Vous pouvez employer le pointer pour lire ou écrire dans cette mémoire.
  4. "Ouvrez" le bloc de mémoire en appelant GlobalUnlock. Cette fonction rend nul et sans effet le pointer du bloc de mémoire.
  5. Libérez le bloc de mémoire en appelant GlobalFree. Cette fonction reçoit l'handle du bloc de mémoire.
Vous pouvez aussi remplacer "Global" par "Local" comme LocalAlloc, LocalLock, etc.
Cette méthode sera davantage simplifiée plus loin en employant le flag 'GMEM_FIXED', dans l'appel de GlobalAlloc. Si vous utilisez ce flag, la valeur de retour de Global/LocalAlloc sera le pointer du bloc de mémoire alloué, et non pas l'Handle du bloque de mémoire. Vous ne devez pas appeler Global/LocalLock puis passer le pointer à Global/LocalFree sans appeler d'abord Global/LocalUnlock. Mais dans ce Tutorial, j'emploierai l'approche "traditionnelle".

Les fichiers d'entrée-sortie sous Win32 ressemblent étonnement à ceux sous DOS. Les étapes nécessaires sont les mêmes. Vous avez seulement besoin de changer les appels aux API, c'est tout. Les étapes exigées sont les suivantes :

  1. Ouvrez ou créez le fichier en appelant la fonction CreateFile. Cette fonction est bourrée de ressources : en plus des fichiers, elle est capable d'ouvrir des ports de communication, des lecteurs de disques ou le clavier. En cas de succès (quand tout ce passe bien), elle renvoie l'handle du fichier ou celui du Device = l'extension matérielle (ex: le clavier). Vous pouvez alors employer cet handle pour exécuter des opérations sur le fichier ou le Device.

    2. Déplacez le pointer dans le fichier à l'emplacement désiré en appelant SetFilePointer.
  2. Exécutez, lisez ou écrivez l'opération en appelant ReadFile ou WriteFile. Ces fonctions transfèrent des données d'un bloc de mémoire vers un fichier et inversement. Donc vous devez allouer(réserver) un bloc de mémoire assez grand pour qu'il puisse contenir les données.
  3. Fermez le fichier en appelant CloseHandle. Cette fonction reçoit l'Handle du fichier.

Contenu:

Le programme inscrit ci-dessous, affiche une boîte de dialogue (OpenFile) qui sert à ouvrir des fichiers. Il laisse l'utilisateur choisir un fichier texte quelconque pour l'ouvrir puis affiche le contenu de ce fichier dans une fenêtre d'édition à l'intérieur de son secteur client. L'utilisateur a la possibilité de modifier le texte dans la zone d'édition à sa guise puis ensuite il peut de retour sauvegarder le contenu dans un fichier.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260
MEMSIZE equ 65535

EditID equ 1 ; n° ID de la zone d'édition.

.data
ClassName db "Win32ASMEditClass",0
AppName db "Win32 ASM Edit",0
EditClass db "edit",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndEdit HWND ? ; L'Handle de la zone d'édition.
hFile HANDLE ? ; Handle du fichier.
hMemory HANDLE ? ; Handle du bloc de mémoire réservé.
pMemory DWORD ? ; Pointer du bloc de mémoire réservé
SizeReadWrite DWORD ? ; Nombre d'octets réellement lus ou écrits. (dans le bloque mémoire on peut très bien ne pas tout utiliser).

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:SDWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc uses ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
0,0,0,hWnd,EditID,\
hInstance,NULL
mov hwndEdit,eax
invoke SetFocus,hwndEdit
;====================================================
; Initialisation des members de la structure OPENFILENAME
;====================================================
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
.ELSEIF uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0ffffh
invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE
.ELSEIF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
invoke SetFocus,hwndEdit
.elseif ax==IDM_SAVE
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetSaveFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
invoke SetFocus,hwndEdit
.else
invoke DestroyWindow, hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
0,0,0,hWnd,EditID,\
hInstance,NULL
mov hwndEdit,eax

Dans la section WM_CREATE, nous créons un contrôle d'édition (ou une zone d'édition, de saisie). Notez que les paramètres x, y, width, height qui sont les dimensions du contrôle sont tous mis à zéros puisque nous redéfinirons leurs tailles plus tard pour que son secteur client soit contenu en entier dans la fenêtre parente.
Remarquez que dans ce cas, nous ne devons pas appeler ShowWindow pour faire apparaître la zone d'édition à l'écran, parce que nous incluons le style WS_VISIBLE. Vous pouvez aussi utiliser ce tour de passe passe pour la fenêtre parente.

;=====================================================
; Initialisation des membres de la structure OPENFILENAME.
;=====================================================
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE

Après la création du contrôle d'édition, prenons le temps d'initialiser les membres d'ofn. Parce que nous souhaitons aussi réutiliser ofn pour la boîte de dialogue de sauvegarde, nous remplissons uniquement les membres *communs* employés par GetOpenFileName et GetSaveFileName.
La section WM_CREATE est un formidable endroit pour pouvoir faire vite fait l'initialisation.

.ELSEIF uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0ffffh
invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE

Nous recevons des messages WM_SIZE lorsque la taille du secteur client de notre fenêtre principale change. Nous recevons aussi ce message la toute première fois que la fenêtre est créée. Pour pouvoir recevoir ce message, les styles de classe de fenêtre doivent inclure les styles CS_VREDRAW et CS_HREDRAW. Nous profitons de cette occasion pour redimensionner notre contrôle d'édition à la taille du le secteur client de notre fenêtre parente. D'abord nous devons connaître la largeur actuelle et la hauteur du secteur client de la fenêtre parente. Nous obtenons ces renseignements de lParam. Le mot de poids fort d'lParam contient la hauteur alors que le mot de poids faible d'lParam contient la largeur du secteur client. Nous employons alors ces deux informations pour redimentionner la zone d'édition en appelant la fonction MoveWindow. En plus du changement de la taille de la fenêtre, nous pouvons aussi changer sa position.

.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn

Quand l'utilisateur choisit le sous-menu File/Open, nous remplaçons les Flags de la structure ofn et appelons la fonction GetOpenFileName pour afficher la boîte de dialogue qui sert à ouvrir des fichiers.

.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax

Après que l'utilisateur ait choisi un fichier pour l'ouvrir, nous appelons CreateFile pour ouvrir ce fichier. Nous indiquons à cette fonction qu'elle doit essayer d'ouvrir le fichier pour le lire ou bien écrire dedans. Après que le fichier soit ouvert, la fonction renvoie l'handle de ce fichier que nous stockons dans une variable Globale pour une future utilisation. Cette fonction a la syntaxe suivante :

CreateFile proto lpFileName:DWORD,\
dwDesiredAccess:DWORD,\
dwShareMode:DWORD,\
lpSecurityAttributes:DWORD,\
dwCreationDistribution:DWORD\,
dwFlagsAndAttributes:DWORD\,
hTemplateFile:DWORD

dwDesiredAccess spécifie quelle est l'opération que vous voulez exécuter sur le fichier.

  • 0 Ouvre le fichier pour interroger ses attributs. Vous avez le droit de lire ou écrire les données.
  • GENERIC_READ Ouvre le fichier pour le lire.
  • GENERIC_WRITE Ouvre le fichier pour pouvoir écrire dedans.
dwShareMode indique quelle opération, sur le fichier ouvert, vous voulez permettre à d'autres process d'effectuer.
  • 0 Empêche le partage du fichier avec d'autres process.
  • FILE_SHARE_READ permet à d'autres process de lire les données, du fichier qui est ouvert.
  • FILE_SHARE_WRITE permet à d'autres process d'écrire des données dans le fichier qui est ouvert.
lpSecurityAttributes n'a aucune signification sous Windows 95.
dwCreationDistribution indique que l'action CreateFile s'exécutera, que le fichier indiqué dans lpFileName existe ou non.
  • CREATE_NEW Crée un nouveau fichier. La fonction échoue si le fichier indiqué existe déjà.
  • CREATE_ALWAYS Crée un nouveau fichier. La fonction recopie le fichier s'il existe.
  • OPEN_EXISTING Ouvre le fichier. La fonction échoue si le fichier n'existe pas.
  • OPEN_ALWAYS Ouvre le fichier, s'il existe. Si le fichier n'existe pas, la fonction crée le fichier comme si dwCreationDistribution était CREATE_NEW.
  • TRUNCATE_EXISTING Ouvre le fichier. Une fois ouvert, le fichier est tronqué(écourté) pour que sa taille finisse par des octets zéro (ex taille de 400 octets). Le process d'appel doit ouvrir le fichier avec au moins l'accès GENERIC_WRITE. La fonction échoue si le fichier n'existe pas.
dwFlagsAndAttributes indique les attribus du fichier
  • FILE_ATTRIBUTE_ARCHIVE dit que le fichier est un fichier d'archives. Les applications emploient cet attribut pour marquer des fichiers pour des Backup(sauvegardes) ou des déplacements.
  • FILE_ATTRIBUTE_COMPRESSED indique que le fichier ou le dossier est comprimé. Pour un fichier, ça signifie que toutes les données dans le fichier sont comprimées. Pour un dossier, ça signifie que la compression est par défaut pour tous les fichiers et ses sous-répertoires nouvellement créés.
  • FILE_ATTRIBUTE_NORMAL indique que le fichier n'a aucun autre jeu d'attributs. Cet attribut est valable seulement s'il est employé seul.
  • FILE_ATTRIBUTE_HIDDEN indique que c'est un fichier caché. Il ne doit pas figurer dans une inscription de renseignements ordinaire.
  • FILE_ATTRIBUTE_READONLY le fichier ne peut être qu'uniquement lu. Les applications peuvent lire le fichier, mais ne peuvent pas y écrire ni le supprimer.
  • FILE_ATTRIBUTE_SYSTEM le fichier fait parti ou bien est exclusivement employé par le système d'exploitation. c'est donc un fichier 'system'.
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax

Quand le fichier est ouvert, nous allouons(réservons) un bloc de mémoire pour l'utiliser par les fonctions WriteFile ou ReadFile. Nous spécifions le flag GMEM_MOVEABLE pour que ce soit Windows qui s'occupe de déplacer le bloc de mémoire dans le but de consolider la mémoire (défragmentation locale). Le flag GMEM_ZEROINIT dit à GlobalAlloc de remplir le bloc de mémoire nouvellement réservé avec des zéros.
Quand les retours de GlobalAlloc sont menés avec succès, eax contiennent l'handle du bloc de mémoire alloué. Nous passons cet handle à la fonction GlobalLock qui renvoie ensuite le pointer au bloc de mémoire.

invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory

Quand le bloc de mémoire est prêt pour l'utilisation, nous appelons la fonction ReadFile pour lire les données du fichier. Quand un fichier est d'abord ouvert ou créé, le fichier n'est pas. Ainsi dans ce cas, nous commençons à lire le premier octet dans le fichier. Le premier paramètre de ReadFile est l'handle du fichier à lire, le deuxième est le pointer du bloc de mémoire qui content les données, ensuite c'est le nombre d'octets à lire dans le fichier, le quatrième paramètre est l'adresse de la variable (de taille DWORD) qui contiendra le nombre d'octets réellement lus dans le fichier.
Après que nous ayons remplis le bloc de mémoire des données, nous transvasons ces données vers le contrôle d'édition (la zone de saisie) en envoyant le message WM_SETTEXT au contrôle d'édition, avec lParam qui contient le pointer du bloc de mémoire. Après cet appel, le contrôle d'édition affiche les données dans son secteur client.

invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif

À ce point, nous n'avons aucun besoin de maintenir le fichier ouvert plus longtemps puisque notre but est d'écrire dans un autre fichier (que le fichier original)les données visibles dans contrôle d'édition. Donc nous fermons le fichier en appelant CloseHandle avec l'Handle du fichier comme son paramètre. Ensuite nous ouvrons le bloc de mémoire et le libérons. En réalité vous ne devez pas libérer la mémoire dès maintenant, vous pourrez réutiliser plus tard le bloc de mémoire pendant l'opération de sauvegarde. Mais dans le but de notre démonstration, je tiens à la libérer ici.

invoke SetFocus,hwndEdit

Quand la DialogBox (qui sert à ouvrir des fichiers) s'affiche à l'écran, le centre d'entrée passe de la fenêtre principale à la DialogBox en question pour que ce soit elle qui soit active. Ainsi quand on referme un fichier de la DialogBox (déjà ouvert), nous devons rendre le centre d'entrée à la zone de saisie (au contrôle d'édition), qui se trouve en arrière.
Ceci fini l'opération de lecture sur le fichier. À ce point, l'utilisateur peut écrire sur le contrôle d'édition. Et quand il veut sauvegarder les données dans un autre fichier, il doit choisir le sous-menu File/Save qui affiche une DialogBox pour les sauvegardes. La création de la boîte de dialogue de sauvegarde n'est pas très différente de la boîte de dialogue d'ouverture de fichiers. En fait, elles diffèrent seulement entre le nom des fonctions, GetOpenFileName et GetSaveFileName. Vous pouvez aussi réutiliser la plupart des membres de la structure ofn, sauf le membre Flag.

mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY

Dans notre cas, nous voulons créer un nouveau fichier, donc OFN_FILEMUSTEXIST et OFN_PATHMUSTEXIST doivent être omis sinon la boîte de dialogue ne nous laissera pas créer un fichier qui n'existe pas déjà.
Le paramètre dwCreationDistribution de la fonction CreateFile doit être changé en CREATE_NEW puisque nous voulons créer un nouveau fichier.
Le reste du code est identique à celui dans la partie 'fichiers à ouvrir' sauf la chose suivante :

invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL

Nous envoyons le message WM_GETTEXT au contrôle d'édition pour que celui-ci puisse recopier les données (le texte) qu'il contient dans le bloc de mémoire que nous avions alloués, la valeur de retour dans eax est la longueur des données à l'intérieur du buffer. Après que les données soient dans le bloc de mémoire, nous les écrivons dans un nouveau fichier.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 13: Memory Mapped Files

Tutorial 13 : Fichiers à Mémoire Configurée

(Memory Mapped Files)

Je vais vous montrer ce que sont les fichiers à mémoires configurées et comment les employer à votre avantage. L'utilisation d'un fichier à mémoire configurée est très facile comme vous le verrez dans ce Tutorial.

Downloadez l'exemple ici.

Aparté : Le procédé de File Mapping ou en français 'Mapper un Fichier en mémoire' c'est en faite faire une copie en mémoire d'un fichier qui était jusque là bien physique. Par exemple un fichier *.txt (physique) peut être entièrement copié quelque part en mémoire (il est Mappé) ce qui présente de gros avantages.

Théorie:

Si vous examinez l'exemple du Tutorial précédent, vous constaterez qu'il a un défaut sérieux : que se passe-t-il si la taille du fichier vous voulez lire est plus grande que le bloc de mémoire allouée ? Ou que la chaîne de caractère que vous voulez chercher est coupée en deux à la fin du bloc de mémoire ? La réponse traditionnelle pour la première question c'est que vous devez à plusieurs reprises lire dans les données du fichier avant que l'on ne rencontre la fin du fichier. La réponse à la deuxième question c'est que vous devez vous préparer à ce cas spécial de fin de bloc de mémoire. ça s'appelle un problème de valeur aux frontières. C'est un vrai casse tête pour les programmeurs car ça engendre des bogues (des défauts) innombrables.
il n'y aurait plus aucun problème si nous pouvions réserver un très grand bloc de mémoire, assez grand pour stocker le fichier en entier. Cependant notre programme s'accaparerait trop de ressources. Ce sont les (Files Mapping) configurations de fichiers qui vont nous sauver. Pour utiliser la configuration de fichier, vous pouvez vous imaginer que c'est un fichier entier, étant déjà chargé en mémoire et vous pouvez employer un pointer de mémoire pour lire ou écrire des données dans ce fichier. C'est aussi simple que cela. Nul besoin d'employer des fonctions API de mémoire et pas besoin de faire de différence entre les fonctions API de fichiers d'entrée et de sortie non plus, ce sont les mêmes dans la configuration de fichiers (Files Mapping). La configuration de fichier est aussi employée comme moyen de partage des données entre plusieurs process (entre plusieurs programmes). En employant la configuration de fichiers de cette façon, il n'y aucun fichier réellement impliqué. C'est plutôt dans le bloc de mémoire réservé que chaque process peut *lire ou écrire*. Mais le partage de ces données entre plusieurs process est un point délicat, ça ne doit pas être traité à la légère. Vous devez exécuter ces process par ordre (les uns après les autres) en les synchronisant, sinon vos applications planteront très vite.
Dans ce Tutorial, nous ne verrons pas le sujet de 'fichier configuré' (File Mapping) en tant que moyen pour créer une zone de mémoire partagée. Nous allons plutôt voir comment utiliser le 'File Mapping' comme moyen de "Mapper" un fichier en mémoire. En fait, le 'PE Loader' emploie la configuration de fichier (File Mapping) pour charger des fichiers exécutables en mémoire. C'est très commode uniquement pour les parties du fichier nécessaires, qui doivent être sélectivement lues sur le disque. Sous Win32, vous devez employer le File Mapping autant que possible.
Il y a quelques limitations à la configuration de fichier, quoique. Une fois que vous créez un fichier à mémoire configurée, sa taille ne peut pas être changée pendant cette session. Donc le 'File Mapping' est important pour les fichiers 'Read-Only' (une des propriété d'un fichier) ou pour des opérations sur des fichiers qui ne doivent en aucun cas affecter la taille du fichier. Ça ne signifie pas que vous ne pouvez pas employer le File Mapping lorsque vous voulez augmenter la taille du fichier. Vous pouvez estimer sa nouvelle taille et prendre comme mémoire cette taille pour votre fichier configuré, ainsi le fichier s'agrandira à cette taille. C'est pas très pratique, c'est tous.
Assez d'explications. On va plonger dans l'exécution de configuration de fichier (File Mapping). Pour employer le (File Mapping), on doit suivre ces étapes :
  1. Appelez CreateFile pour ouvrir le fichier que vous voulez Mapper.
  2. Appelez CreateFileMapping avec l'Handle du fichier renvoyé par CreateFile comme un de ses paramètres. Cette fonction crée un objet 'File Mappinf' du fichier ouvert par CreateFile.
  3. Appelez MapViewOfFile pour Mapper une région du fichier choisie ou bien du fichier entier en mémoire. Cette fonction renvoie le pointer du premier octet de la zone où commence le fichier configuré (Mappé).
  4. Employez le pointer pour lire ou écrire dans le fichier.
  5. Appelez UnmapViewOfFile pour Unmapper le fichier.
  6. Appelez CloseHandle avec l'Handle du fichier configuré (l'Handle du File Mapping) comme paramètre pour fermer le fichier configuré.
  7. Appelez CloseHandle une fois de plus, mais cette fois-ci avec l'Handle renvoyé par CreateFile pour fermer le fichier réel.

Exemple:

Le programme ci-dessous vous laisse ouvrir un fichier grâce à une DialogBox. Il ouvre le fichier en employant le principe du 'File Mapping', si le fichier est ouvert avec succès, le titre de fenêtre principale est changé par nom du fichier qui vient d'être ouvert. Vous pouvez ensuite sauvegarder le fichier sous un autre nom par le biais du sous-menu File/save. Le programme recopiera le contenu total du fichier ouvert, vers un nouveau fichier. Notez que vous ne devez pas appeler GlobalAlloc pour réserver un bloc de mémoire dans ce programme.

.386
.model flat,stdcall
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260

.data
ClassName db "Win32ASMFileMappingClass",0
AppName db "Win32 ASM File Mapping Example",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
hMapFile HANDLE 0 ; Handle du 'memory mapped file', doit être
; initialisé avec un 0 parce qu'on doit aussi l'utiliser comme
; un flag dans la section WM_DESTROY

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hFileRead HANDLE ? ; Handle du fichier source (fichier réel)
hFileWrite HANDLE ? ; Handle du fichier de 'sortie'.
hMenu HANDLE ?
pMemory DWORD ? ; Pointer sur les données dans le fichier source.
SizeWritten DWORD ? ; Nombre d'octes actuellement écrit par WriteFile. (écrit dans la Box
; où on peut mettre du texte)

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke GetMenu,hWnd ; Obtient l'handle du menu
mov hMenu,eax
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFileRead,eax
invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL
mov hMapFile,eax
mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
.endif
.elseif ax==IDM_SAVE
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetSaveFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFileWrite,eax
invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
mov pMemory,eax
invoke GetFileSize,hFileRead,NULL
invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL
invoke UnmapViewOfFile,pMemory
call CloseMapFile
invoke CloseHandle,hFileWrite
invoke SetWindowText,hWnd,ADDR AppName
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
.endif
.else
invoke DestroyWindow, hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp

end start

Analyse:

invoke CreateFile,ADDR buffer,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL

Quand l'utilisateur choisit un fichier dans la DialogBox qui sert à ouvrir des fichiers, nous appelons CreateFile pour l'ouvrir. Notez que nous spécifions GENERIC_READ pour ouvrir ce fichier avec comme propriété 'Read-Only'. 'dwShareMode' est mis à zéro parce que nous ne voulons pas que d'autres process n'aient accès au fichier pendant notre opération.

invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL

Alors nous appelons CreateFileMapping pour Mapper en mémoire le fichier(réel) ouvert. La syntaxe de CreateFileMapping est la suivante :

CreateFileMapping proto hFile:DWORD,\
lpFileMappingAttributes:DWORD,\
flProtect:DWORD,\
dwMaximumSizeHigh:DWORD,\
dwMaximumSizeLow:DWORD,\
lpName:DWORD

Sachez déjà, que CreateFileMapping n'est pas obligé de Mapper la totalité de la mémoire du fichier. Vous pouvez utiliser cette fonction pour Mapper seulement une partie de la mémoire du fichier réel. Vous spécifiez la taille de la mémoire du fichier à Mapper dans les paramètres dwMaximumSizeHigh et dwMaximumSizeLow. Si vous indiquez une taille plus grande que le fichier réel, le fichier réel sera agrandit à la nouvelle taille. Si vous voulez que la taille de la mémoire du 'File Mapping' soit la même que celle du fichier réel, mettez ces deux derniers paramètres à zéro.
Vous pouvez employer un NUL dans le paramètre lpFileMappingAttributes pour que ce soit Windows qui crée le 'Fichier Mappé' avec des attributs de sécurité par défaut.
FlProtect définit la protection désirée pour le 'Fichier Mappé'. Dans notre exemple, nous employons PAGE_READONLY pour permettre uniquement l'opération de lecture sur le 'Fichier Mappé'. Notez que cet attribut ne doit pas contredire l'attribut employé dans CreateFile sinon CreateFileMapping échouera.
LpName pointe sur le nom de le 'Fichier Mappé'. Si vous voulez partager ce fichier avec d'autre process, vous devez lui donner un nom. Mais dans notre exemple, notre process est le seul qui emploie ce fichier donc nous ignorons ce paramètre.

mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax

Si CreateFileMapping s'effectue avec succès, le nom du fichier ouvert prend la place du titre de la fenêtre. Le nom du fichier ainsi que son chemin complète sont stocké dans le buffer, nous voulons afficher uniquement le nom de fichier dans le titre donc nous devons ajouter la valeur du membre nFileOffset de la structure OPENFILENAME à l'adresse de ce buffer. (vous avez compris pourquoi ! ! !)

invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED

Comme précaution, nous ne voulons pas que l'utilisateur puisse ouvrir plusieurs fichiers les uns après les autres, donc nous devons griser le sous-menu 'Open' et dégriser le sous-menu 'Save'. EnableMenuItem est justement employé pour changer les attributs du menu..
Après ceci, nous attendons que l'utilisateur clique sur le sous-menu File/Save pour fermer notre programme. Au moment où l'utilisateur ferme le programme, nous devons en fait fermer le 'Fichier Mappé' ainsi que le fichier réel. Voici comment faire :

.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL

Dans le susdit petit bout de code, quand la procédure de fenêtre reçoit le message WM_DESTROY, il vérifie d'abord la valeur d'hMapFile pour voir si c'est un zéro ou non. Si ce n'est pas le zéro, il appelle la fonction CloseMapFile qui contient le code suivant :

CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp

CloseMapFile ferme le 'Fichier Mappé' et le fichier réel, pour empêcher qu'il n'y ait une fuite de ressources lorsqu'on sort de notre programme Win32.
Si l'utilisateur veut sauvegarder les données dans un autre fichier, le programme l'affiche dans une DialogBox spéciale 'Sauvegarde'. Après qu'il ait tapé le nom du nouveau fichier, le fichier est créé par la fonction CreateFile.

invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
mov pMemory,eax

Immédiatement après que le fichier ait été créé, nous appelons MapViewOfFile pour Mapper la partie désirée du 'Fichier Mappé' en mémoire. Cette fonction a la syntaxe suivante :

MapViewOfFile proto hFileMappingObject:DWORD,\
dwDesiredAccess:DWORD,\
dwFileOffsetHigh:DWORD,\
dwFileOffsetLow:DWORD,\
dwNumberOfBytesToMap:DWORD

dwDesiredAccess indique quelle opération nous souhaitons faire sur le fichier. Dans notre exemple, nous voulons seulement lire les données, donc nous employons FILE_MAP_READ.
dwFileOffsetHigh et dwFileOffsetLow indiquent l'offset de départ où commence le fichier que vous voulez Mapper en mémoire. Ici, nous voulons lire dans la totalité du fichier donc nous commençons le Mapping à l'offset 0.
dwNumberOfBytesToMap indique le nombre d'octets à Mapper en mémoire. Si vous voulez Mapper le fichier en entier (Spécifié par CreateFileMapping), donnez la valeur 0 à MapViewOfFile.
Après l'appel de MapViewOfFile, la partie désirée est chargée en mémoire. On vous renverra le pointer du bloc de mémoire qui contient les données du fichier.

invoke GetFileSize,hFileRead,NULL

Regarde de quelle taille est le fichier. La taille du fichier est renvoyée dans eax. Si le fichier est plus grand que 4 GB, le mot de poids fort de la taille du fichier est stocké dans FileSizeHighWord. Puisque nous ne nous attendons pas à manipuler un si grand fichier, nous pouvons l'ignorer.

invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL

Écrit des données dans le fichier Mappé en Mémoire.

invoke UnmapViewOfFile,pMemory

Quand tout est près avec le fichier d'entrée, on l''Unmappe' de la mémoire.

call CloseMapFile
invoke CloseHandle,hFileWrite

et on referme tous les fichiers.

invoke SetWindowText,hWnd,ADDR AppName

Restore le titre original de la fenêtre principale.

invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED

Réactive le sous-menu 'Open' et désactive le sous-menu 'Save' en le grisant.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 14: Process

Tutorial 14: Process

Nous allons voir ce qu'est un process et comment faire pour le créer et le terminer.

Downloadez l'exemple ici. Attention en fait cet exemple est dans un *.ZIP et il ne marchera pas si vous le laissez dedans, donc dézippez-le.

Préliminaire:

Qu'est ce qu'un 'process' ? Je cite cette définition de la référence Win32 API:
"Un 'process' est une application (un programme) qui sert à exécuter un autre programme. C'est un espace d'adresses virtuel privé, de code, de données et d'autres ressources du système d'exploitation, comme les fichiers, ou la synchronisation d'objets qui sont visibles pour le process.."
Comme vous pouvez le voir dans la définition ci-dessus, un process "possède" plusieurs objets : l'espace d'adresse, le(s) module(s) (les autres programmes ou dll) à exécuter et tout ce que ces programmes créent ou ouvrent. Au minimum, un process doit consister en l'exécution d'un seul module, avec un espace d'adresse privé et un lien. Chaque process doit avoir au moins un lien. Qu'est ce qu'un lien ? Un lien est en réalité une suite d'instructions d'exécution en attente. Lorsque Windows crée un premier process, il crée seulement un lien de process. D'habitude, ce lien fait en sorte d'exécuter la première instruction dans le module (dans le programme ciblé). Si le process à besoins de plusieurs autres liens par la suite, il peut les créer.
Quand Windows reçoit une commande pour créer un process, il alloue un espace d'adresses mémoire privé et ensuite il Mappe (recopie) le fichier exécutable dans cet espace. Après cela il crée le premier lien du process.
Sous Win32, vous pouvez aussi créer des process de vos propres programmes en appelant la fonction CreateProcess. CreateProcess a la syntaxe suivante :

CreateProcess proto lpApplicationName:DWORD,\
lpCommandLine:DWORD,\
lpProcessAttributes:DWORD,\
lpThreadAttributes:DWORD,\
bInheritHandles:DWORD,\
dwCreationFlags:DWORD,\
lpEnvironment:DWORD,\
lpCurrentDirectory:DWORD,\
lpStartupInfo:DWORD,\
lpProcessInformation:DWORD

Ne soyez pas effrayez par le nombre de paramètres. Nous pouvons ignorer la plupart d'entre eux.

LpApplicationName est le nom du fichier exécutable avec ou sans nom de chemin que vous voulez exécuter. Si ce paramètre est nul, vous devez mettre le nom du fichier exécutable dans le paramètre lpCommandLine.
LpCommandLine sont les arguments de la ligne de commande du programme que vous voulez exécuter. Notez que si lpApplicationName est NULL, ce paramètre doit en plus contenir le nom du fichier exécutable. Comme cela : "notepad.exe readme.txt". (LpCommandLine c'est aussi l'argument qu'on écrit dans W32Dasm quand on fait 'Debug --> Load Process [argument] --> Load' , mais c'est très rare qu'on écrive quelque chose en tant qu'argument!)
LpProcessAttributes et lpthreadAttributes Spécifient les attributs de sécurité pour le process et le lien primaire. S'ils sont NULL, les attributs de sécurité par défaut sont employés.
BInheritHandles est un flag qui indique si vous voulez qu'un autre process hérite du contrôle de toutes les manipulations ouvertes par votre process.
DwCreationFlags représente Plusieurs flags qui déterminent le comportement du process que vous voulez créé, comme : voulez-vous qu'un process soit créés, mais suspendu immédiatement pour que vous puissiez l'examiner ou le modifier avant qu'il ne reparte ? Vous pouvez aussi spécifier la classe de priorité du lien(s) dans un nouveau process. Cette classe de priorité est employée pour déterminer la priorité de planification des liens du process. Normalement nous employons le flag NORMAL_PRIORITY_CLASS.
LpEnvironment est le pointer du bloc d'environnement qui contient plusieurs instructions pour un nouveau process. Si ce paramètre est NULL, le nouveau process hérite de l'environnement du bloc process précédent.
LpCurrentDirectory est le pointer qui est sur la chaîne de caractères lequel indique l'actuel 'Lecteur ' (Drive) ainsi que le répertoire (Directory) du Child Process. Il est NULL si vous voulez que le 'Child Process' succède au 'Parent Process'.
LpStartupInfo pointe sur une structure STARTUPINFO laquelle dit comment doit apparaître la fenêtre principale du nouveau process. La structure STARTUPINFO contient beaucoup de membres servants à définir l'affichage de la fenêtre principale du 'Child Process'. Si vous ne voulez rien de spécial, vous pouvez remplir la structure STARTUPINFO avec les valeurs du 'Parent Process' en appelant la fonction GetStartupInfo.
LpProcessInformation pointe sur la structure PROCESS_INFORMATION laquelle reçoit l'information d'identification du nouveau process. La structure de PROCESS_INFORMATION est la suivante :

PROCESS_INFORMATION STRUCT
hProcess HANDLE ? ; Handle du 'Child Process'
hThread HANDLE ? ; Handle du premier lien du 'Child Process'
dwProcessId DWORD ? ; ID du 'Child Process'
dwThreadId DWORD ? ; ID du premier lien du 'Child Process'
PROCESS_INFORMATION ENDS
L'handle du process et l'ID du process sont deux choses différentes. L'ID est un identificateur unique du process dans le système. L'handle est une valeur renvoyée par Windows pour qu'on puisse l'utiliser avec d'autres process rapportés de fonctions API. L'handle du process ne peut pas être employé pour identifier un process puisque il n'est pas unique.

Après l'appel de CreateProcess, un nouveau process est créé et on retourne (au prog principal) immédiatement après cet appel de CreateProcess. Vous pouvez vérifier si le nouveau process est toujours actif en appelant la fonction GetExitCodeProcess qui a la syntaxe suivante :

GetExitCodeProcess proto hProcess:DWORD, lpExitCode:DWORD

Si cet appel est couronné de succès, lpExitCode contient le statut de terminaison du process en question. Si la valeur dans lpExitCode est égale à STILL_ACTIVE, alors ce process est toujours en cours.

Vous pouvez forcer le process à se terminer en appelant la fonction TerminateProcess. Voici sa syntaxe :

TerminateProcess proto hProcess:DWORD, uExitCode:DWORD

Vous pouvez spécifier le code de sortie désiré pour votre process, n'importe quelle valeur que vous souhaitez. TerminateProcess n'est pas une façon très propre de terminaison d'un process puisque chaque dll rattachée à ce process ne sera pas informée que le process a été fermé.

Exemple:

L'exemple suivant crée un nouveau process lorsque l'utilisateur choisit le sous-menu (le menu item) "Create Process". Il essayera d'exécuter "msgbox.exe". Si l'utilisateur souhaite terminer le nouveau process, il peut choisir le menu item "Terminate Process". Le programme vérifiera d'abord si le nouveau process est déjà fermé, si ce n'est pas le cas, le programme appellera la fonction TerminateProcess pour détruire le nouveau process.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.const
IDM_CREATE_PROCESS equ 1
IDM_TERMINATE equ 2
IDM_EXIT equ 3

.data
ClassName db "Win32ASMProcessClass",0
AppName db "Win32 ASM Process Example",0
MenuName db "FirstMenu",0
processInfo PROCESS_INFORMATION <>
programname db "msgbox.exe",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HANDLE ?
ExitCode DWORD ? ; Contient le statut 'exitcode du process' servant à l'appel de GetExitCodeProcess.

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL startInfo:STARTUPINFO
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_INITMENUPOPUP
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_CREATE_PROCESS
.if processInfo.hProcess!=0
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_PRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread
.elseif ax==IDM_TERMINATE
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if ExitCode==STILL_ACTIVE
invoke TerminateProcess,processInfo.hProcess,0
.endif
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

Ce programme crée la fenêtre principale et renvoie l'handle de son menu pour une future utilisation. Il attend alors que l'utilisateur choisisse une commande du menu. Lorsque l'utilisateur choisit le sous-menu "Process" du menu principal (de la barre de menu), nous déployons le menu grâce au message WM_INITMENUPOPUP pour modifier les sous-menus à l'intérieur du 'menu popup' (menu principal) avant qu'il ne soit affichés.

.ELSEIF uMsg==WM_INITMENUPOPUP
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif

Pourquoi utilisons-nous ce message dans notre process? Parce que nous souhaitons préparer les sous-menus dans le menu popup avant que l'utilisateur ne puisse les voir. Dans notre exemple, lorsque le process n'est pas encore lancé, nous activons le sous-menu "Start Process" et désactivons "Terminate Process" (il est grisé). On fait le contraire quand le nouveau process est déjà actif.
Nous vérifions d'abord si le nouveau process dirige (a encore la main sur MsgBox.exe) toujours en appelant la fonction GetExitCodeProcess avec l'handle du process qui a été renvoyé par la fonction CreateProcess. Si les retours de GetExitCodeProcess sont FALSE(FAUX), alors ça signifie que notre process n'est pas encore commencé, donc nous grisons (désactivons) le sous-menu "Terminate Process". Si les retours de GetExitCodeProcess sont TRUE(VRAIS), nous savons que notre nouveau process a été lancé, mais nous devons vérifier plus loin s'il est toujours en court. Donc nous comparons la valeur dans ExitCode à la valeur STILL_ACTIVE. S'ils sont égaux,c'est que notre process en encore en court : nous devons alors désactiver le sous-menu "Start Process" puisque nous ne souhaitons pas lancer plusieurs process en même temps.

.if ax==IDM_CREATE_PROCESS
.if processInfo.hProcess!=0
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_PRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread

Quand l'utilisateur choisit le menu item "Start Process", nous vérifions d'abord si le membre hProcess de la structure PROCESS_INFORMATION est déjà fermé. Si c'est la première fois, la valeur d'hProcess sera toujours zéro puisque nous définissons la structure de PROCESS_INFORMATION dans la section '.data'. Si la valeur du membre hProcess n'est pas 0, ça signifie que le Child Process est sorti mais nous n'avons pas fermé son handle process encore. Donc c'est maintenant qu'il le faire.
Nous appelons la fonction GetStartupInfo pour récupérer la valeur startupinfo pour pouvoir ensuite la passer à la fonction CreateProcess. Après ça nous appelons la fonction CreateProcess pour commencer un nouveau process. Notez que je n'ai pas vérifié la valeur de retour de CreateProcess parce que ça aurait encore compliqué cet exemple. En réalité, vous devriez vérifier cette valeur de retour (de CreateProcess). Immédiatement après CreateProcess, nous fermons l'handle du premier lien, lequel est renvoyé par la structure processInfo. La fermeture du handle ne veut pas dire que est le lien terminé, mais seulement que nous ne voulons pas employer cet handle pour nous référer au lien de notre programme. Si nous ne le fermons pas, il provoquera une fuite des ressources.

.elseif ax==IDM_TERMINATE
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if ExitCode==STILL_ACTIVE
invoke TerminateProcess,processInfo.hProcess,0
.endif
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0

Quand l'utilisateur choisit le sous-menu (le menu item) "Terminate Process", nous vérifions si le nouveau process est toujours actif en appelant la fonction GetExitCodeProcess. Si il est toujours actif, nous appelons la fonction TerminateProcess pour détruire notre process. Aussi nous fermons l'handle du Child Process puisque désormais nous n'en avons plus besoin.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 15: Multithreading Programming

Tutorial 15: Programmation Multi-liens

Dans ce Tutorial, nous allons voir comment créer un programme multi-liens. On va étudier aussi les méthodes de communication entre les liens.

Downloadez l'exemple ici.

Théorie:

Dans le Tutorial précédent, vous avez appris ce qu'était le lien minimum pour un Process: le lien primaire. Un lien est une chaîne d'exécution. Mais vous pouvez aussi créer des liens supplémentaires dans votre programme. Vous pouvez vous représenter le multi-liens comme une multigestion d'un programme. En terme d'exécution, un lien est une fonction qui tourne en parallèle avec le programme principal. Vous pouvez ouvrir puis commander plusieurs fois la même fonction ou bien vous pouvez diriger plusieurs fonctions simultanément selon vos exigences. Le multi-liens est spécifique à Win32, ça n'existe pas sous Win16.
Par exemple, le multi-liens c'est quand un Process ouvre plusieurs fois une même MessageBox (en changent son contenu si on le souhaite) ou bien quand une MessageBox est appelée par plusieurs process différents.
Les liens sont dirigés par un même process donc ils peuvent avoir accès à n'importe quelles ressources de ce process, telles que les variables globales, les Handles etc. Cependant, chaque lien a sa propre délimitation dans le programme, donc les variables locales de chaque liens sont privées vis à vis des autres liens. Chaque lien possède aussi son jeu de registres privé, donc quand Windows se sert d'autres liens, le lien peut "se rappeler" son statut précédant et peut "reprendre" son ancienne tâche quand il regagne son contrôle. Tout ça est traité intérieurement par Windows.
Voilà ce que permet le multi-liens. Dans les deux programmes 'Thread.exe' et 'Thread1.exe' on leur fait exécuter un calcul très lourd qui les occupe. Pour 'Thread.exe' qui ne possède qu'un lien simple ne peut plus rien faire d'autre que son calcul quand il est commencé, il ne répond plus aux actions de l'utilisateur comme déplacer sa fenêtre pendant le calcul.
'Thread1.exe' qui lui possède un système multi-liens, se sert de lien de travail pour faire le calcul mais laisse un lien principale pour répondre aux sollicitations de l'utilisateur. Comme ça, il répond encore, même pendant une lourde tache.
Nous pouvons diviser les liens en deux catégories :
  1. Les liens d'interface pour l'utilisateur : Ce type de liens crée sa propre fenêtre donc il reçoit des messages spécifiques aux fenêtres. Il peut répondre à l'utilisateur via sa propre fenêtre, de là son nom. Ce type de liens est soumis à la règle(autorité) Mutex de Win16 qui permet un seul lien d'interface d'utilisateur, en utilisation 16 bits avec le noyau gdi. alors qu'un lien d'interface d'utilisateur exécute le code d'utilisateur 16 bits avec le noyau gdi, les autres liens UI ne peuvent pas utiliser le service d'utilisateur 16 bits avec noyau gdi. Notez que ce Mutex Win16 est spécifique à Windows 95. Windows NT ne possède pas de Mutex Win16, c'est pourquoi les liens d'interface d'utilisateur de Windows NT s'effectuent plus doucement que sous Windows 95.
  2. Les liens de travail : Ce type de liens ne créent pas de fenêtre donc ils ne peuvent pas recevoir de message spécifiques aux fenêtres. Ils existent principalement pour faire le travail assigné en arrière-plan, de là leur nom.
Je conseille la stratégie suivante pour utiliser la pleine capacité des multi-liens sous Win32 : Laissez le lien primaire faire le truc d'interface d'utilisateur et les autres liens faire le travail complexe en arrière-plan. De cette façon, le lien primaire servira de 'Gouverneur', et les autres liens seront un peu comme le 'personnel du Gouverneur'. Le Gouverneur délègue ses responsabilités à son personnel tandis qu'il maintient le contact avec le public(l'utilisateur). Le personnel du Gouverneur exécute avec obéissance le travail et l'annonce de ses résultats. Si le Gouverneur (le lien primaire) devait exécuter chaque tâche lui-même, il ne serait pas capable de donner beaucoup d'attention au public ou à la presse. Tout ça s'apparente à une fenêtre qui a pour action de donner un long travail pour occupée son lien primaire : il ne répond pas à l'utilisateur avant que le travail ne soit achevé. Un tel programme peut en profiter pour créer un lien additionel qui sera responsable du long travail, permettant ainsi au lien primaire de répondre aux commandes de l'utilisateur.
Nous pouvons créer un lien en appelant la fonction CreateThread, laquelle a la syntaxe suivante :

CreateThread proto lpThreadAttributes:DWORD,\
dwStackSize:DWORD,\
lpStartAddress:DWORD,\
lpParameter:DWORD,\
dwCreationFlags:DWORD,\
lpThreadId:DWORD

Le fonctionnement de CreateThread ressemble beaucoup à celui de CreateProcess.
lpThreadAttributes Vous pouvez le mettre à 0 (NULL) si vous souhaitez que le lien soit le descripteur de sécurité par défaut.
dwStackSize Ça spécifie la taille à réserver sur la pile pour le lien. Si vous voulez que le lien ait la même taille de pile que le lien primaire, employez le 0 (NULL) pour ce paramètre.
lpStartAddress Adresse de la fonction qui fait le 'lien'. C'est la fonction qui exécutera le travail (qui fera le lien). Cette fonction DOIT recevoir un et seulement un paramètre 32 bits et renvoyer une valeur sur 32 bits.
lpParameter C'est le paramètre que vous devez passer à la fonction 'lien'.
dwCreationFlags 0 signifie que le lien prend le commandement immédiatement après qu'il est été créé. L'opposé c'est le flag 'CREATE_SUSPENDED'.
lpThreadId La fonction 'CreateThread' donnera l'ID du nouveau lien créé à cette adresse.

Si l'appel de CreateThread s'est déroulé avec succès, il rend l'handle du lien nouvellement créé. Sinon, il renvoie le NULL.
Le lien est en fonction aussitôt après que l'appel de CreateThread soit un succès, à moins que vous ne mettiez le flag 'CREATE_SUSPENDED' dans dwCreationFlags. Dans ce cas, le lien est suspendu jusqu'à ce que la fonction ResumeThread soit appelée.
Après les retours de la 'fonction de lien' (avec l'instruction ret), Windows appelle la fonction ExitThread tout seul. Mais vous pouvez appeler la fonction ExitThread dans votre 'fonction de lien' par vous-même, mais là je développerai pas !
Vous pouvez retrouver le code de sortie d'un lien en appelant la fonction GetExitCodeThread.
Si vous voulez terminer un lien, vous pouvez appeler la fonction TerminateThread. Mais vous devez employer cette fonction seulement dans des circonstances extrêmes puisque cette fonction termine le lien immédiatement sans lui donner aucune chance de se fermer proprement, sans laissez derrière lui des valeurs qui lui sont spécifiques dans certaines variables.

Maintenant on va vous montrer les méthodes de communications entre les liens.
Il y en a trois:

  • L'utilisation des variables globales
  • Les messages Windows
  • Les événements
Les liens partagent les ressources du process en incluant des variables globales donc les liens peuvent utiliser ces varibles globales pour communiquer les uns avec les autres. Cependant cette méthode doit être utilisée avec grand soin. La synchronisation des liens doit être prise en compte. Par exemple, si deux liens emploient la même structure de 10 membres, ce qui arrive quand Windows prend soudainement le contrôle d'un lien lorsqu'il est en pleine remise à jour de sa structure, l'autre lien sera alors abandonné avec des données incomplètes ou sans significations pour sa structure! Ne faites pas d'erreur, les programmes multi-liens sont bien plus durs à mettre au point et à maintenir. Cette sorte de bogue(défaut) semble bien arriver au hasard, ce qui fait que c'est très difficile à traquer.
Vous pouvez aussi employer des messages de fenêtre pour communiquer entre les liens. Si les liens sont tous des interfaces d'utilisateur, il n'y a aucun problème : cette méthode peut être employée en tant que communication bilatérale. Tout ce que vous devez faire c'est définir un ou plus messages de fenêtres sur mesure lesquels seront spécifiques aux liens. Vous définissez un message sur mesure en employant le message WM_USER comme valeur de base. Voilà, vous pouvez le définir comme ceci :

WM_MYCUSTOMMSG equ WM_USER+100h

Windows n'emploiera jamais aucune valeur WM_USER vers ses propres messages donc vous pouvez employer la valeur WM_USER pour vos propres messages faits sur mesure.
Si un des lien est un lien d'interface utilisateur (le lien primaire, le Gouverneur) et un autre est un des 'personnel du gouverneur', vous ne pouvez pas employer cette méthode en tant que communication bilatérale puisque un lien 'personnel' ne possède pas sa propre fenêtre. Donc il n'a y pas de file d'attente de message. Vous pouvez employer le schéma suivant :

User interface Thread ------> global variable(s)----> Worker thread
Worker Thread ------> custom window message(s) ----> User interface Thread

Soit en Français:

Lien d'interface d'utilisateur (primaire)------> variable(s) globale(s)----> lien de travail (personnel)
lien de travail (personnel)------>'message(s) de fenêtre' conçu sur mesure----> lien d'interface d'utilisateur

En fait, nous employons cette méthode dans notre exemple.
La dernière sorte de communication c'est l'événement objet. Vous pouvez vous représenter l'événement' comme une sorte de flag. Si l'événement est dans l'état "non signalé", le lien est inerte ou en sommeil, dans cet état, le lien ne reçoit pas d'ordre du CPU. Quand l'événement est dans l'état "signalé", Windows "réveille" le lien et il commence à exécuter la tâche assignée.

Exemple:

Vous devez Downloader le fichier *.Zip en exemple et lancer 'thread1.exe'. Cliquez sur le sous-menu (le menu item) "Savage Calculation". Ça ordonnera au programme d'exécuter l'instruction "add eax, eax" 600,000,000 fois. Remarquez que pendant ce temps-là, vous ne pouvez rien faire dans la fenêtre principale : vous ne pouvez pas la déplacer, vous ne pouvez pas activer son menu, etc. Quand le calcul est achevé, une boîte de message apparaît. Après ça, la fenêtre accepte de nouveau vos commandes normalement.
Pour éviter à l'utilisateur ce type d'inconvénient, nous pouvons déplacer la routine "de calcul" dans un lien de travail (personnel du gouverneur) séparé et laisser le lien primaire (le lien gouverneur) continuer avec sa tâche d'interface utilisateur. Vous pouvez voir que bien que la fenêtre principale réponde plus lentement que d'habitude, elle répond quand même toujours.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.const
IDM_CREATE_THREAD equ 1
IDM_EXIT equ 2
WM_FINISH equ WM_USER+100h

.data
ClassName db "Win32ASMThreadClass",0
AppName db "Win32 ASM MultiThreading Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
ThreadID DWORD ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_CREATE_THREAD
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
0,\
ADDR ThreadID
invoke CloseHandle,eax
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSEIF uMsg==WM_FINISH
invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

ThreadProc PROC USES ecx Param:DWORD
mov ecx,600000000
Loop1:
add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out:
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc ENDP

end start

Analyse:

Le programme principal se présente à l'utilisateur en tant qu'une fenêtre normale avec un menu. Si l'utilisateur choisit le menu item "Create Thread", le programme crée un lien comme indiqué ci-dessous :

.if ax==IDM_CREATE_THREAD
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax

La susdite fonction crée un lien qui contrôlera une procédure nommée ThreadProc en parallèle avec le lien primaire. Après que cet appel soit couronné de succès, CreateThread revoie ses valeurs de retours immédiatement et ThreadProc se lance. Puisque nous n'employons pas d'handle de lien, nous devons le fermer sinon il y aura des fuites de mémoire. Notez que la fermeture de l'handle du lien ne termine pas ce lien. Son seul effet est que désormais nous ne pourrons plus employer l'handle du lien.

ThreadProc PROC USES ecx Param:DWORD
mov ecx,600000000
Loop1:
add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out:
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc ENDP

Comme vous pouvez le voir, ThreadProc exécute un calcul sauvage (un calcul immense qui occupe le système) qui prend un longue période de temps avant de se terminer, et quand il est finit il envoie enfin le message WM_FINISH vers la fenêtre principale. WM_FINISH est notre message (celui qu'on a créé sur mesure). Il est ainsi défini :

    WM_FINISH equ WM_USER+100h
Vous n'êtres pas obligez d'ajouter WM_USER et 100h mais c'est plus sûr quand même de faire ça.
Le message WM_FINISH n'a de signification uniquement que dans notre programme. Quand la fenêtre principale reçoit le message WM_FINISH, ça fait apparaître une boîte de message disant que le calcul est achevé.
Vous pouvez créer plusieurs liens en même temps en cliquant sur "Create Thread" plusieurs fois.
Dans cet exemple, la communication est à sens unique, c'est pour ça que c'est le lien principal uniquement, qui peut informer la fenêtre principale. Si vous voulez que le lien principal envoie des commandes au lien de travail, voilà comment faire :
  • Ajoutez un sous-menu disant quelque chose dans le genre " Kill Thread" (détruire le lien)
  • Employez une variable globale en tant que simple commande flag. TRUE = Stop le lien, FALSE = continue le lien
  • Modifiez ThreadProc pour vérifier la valeur de la commande flag dans la boucle.
Quand l'utilisateur choisit le sous-menu "Kill Thread", le programme principal mettra la valeur TRUE dans la commande flag. Quand ThreadProc voit que la valeur de la commande flag est TRUE(VRAI), on sort de la boucle et on rompt ainsi les liens.
[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 16: Event Object

Tutorial 16: Event Object

Nous allons apprendre ce qu'est un event object et comment l'employer dans un programme multi-liens.

Downloadez l'exemple ici.

Théorie:

Dans le Tutorial précédent, on a vu comment les liens communiquent avec un message de fenêtre sur mesure (custum). J'ai omis deux autres méthodes : les variables globales et les 'events objets'. On va utiliser les deux dans ce Tutorial.
Un 'event objet', c'est comme un commutateur : il a seulement deux états possibles: on/off. Quand un 'event objet' est allumé, il est dans l'état "signalé" (c'est à dire qu'on l'utilise). Quand il est éteint, il est dans l'état "nonsignalé"(on ne l'utilise pas, on n'y fait référence nulle part). On créer un 'event objet' et on écrit un petit bout de code au niveau des liens appropriés pour observer l'état de l' 'event objet'. Si l' 'event objet' est dans l'état nonsignalé, les liens qui se servent des 'event objet' sont inactifs. C'est bien pratique car lorsque les liens sont en état d'attente, ils consomment peu de temps CPU.
Vous créez un 'event object' en appelant la fonction CreateEvent qui a la syntaxe suivante :

CreateEvent proto lpEventAttributes:DWORD,\
bManualReset:DWORD,\
bInitialState:DWORD,\
lpName:DWORD

lpEventAttribute--> Si vous donnez la valeur NULL, l' 'event object' est créé d'une façon par défaut.
bManualReset--> Si vous souhaitez que Windows désactive automatiquement l'event object (off) après l'appel WaitForSingleObject, vous devez taper 'FALSE' pour ce paramètre. Sinon vous devez le désactiver manuellement avec l'appel ResetEvent.
bInitialState--> Au moment où il est créé, si vous souhaitez que l'event object soit dans l'état signalé, mettez TRUE pour ce paramètre, sinon l' event object sera créé avec l'état nonsignalé.
lpName --> C'est le pointer qui repère une chaîne de caractère (ASCIIZ=du texte), laquelle est le nom de l'event object. Ce nom sera employé quand vous voudrez appeler OpenEvent.(pointe sur le nom de l'Event Object)

Si l'appel s'est déroulé avec succès, il renvoie l'handle de l' event object qui vient d'être créé, autrement il renvoie le NULL.
Vous pouvez modifier l'état de l'event object avec deux des appels API : SetEvent et ResetEvent. La fonction SetEvent met l'event object à l'état signalé. ResetEvent fait le contraire.
Après que l'event object soit créé, il vous faut insérez l'appel 'WaitForSingleObject' dans le lien qui dépend de l'état de l'event object. WaitForSingleObject a la syntaxe suivante :

WaitForSingleObject proto hObject:DWORD, dwTimeout:DWORD

hObject --> L'handle d'un des l'objets de synchronisation. L' event object est un type d'objet de synchronisation.
dwTimeout --> C'est à vous de fixer (en millisecondes) le temps maximal que cette fonction peut mettre pour que l'objet passe à l'état signalé. Si le temps indiqué est passé et que l'event object est toujours à l'état nonsignalé, alors WaitForSingleObject rend la main à l'interlocuteur. Si vous voulez attendre indéfiniment, vous devez mettre la valeur 'INFINITE' pour ce paramètre.(c'est aussi -1 en décimal ou FFFFFFFF en hexadécimal).

Exemple:

L'exemple ci-dessous montre une fenêtre attendant que l'utilisateur choisisse une commande du menu. Si l'utilisateur choisit ''run thread'' (lien en route), le lien commence le calcul sauvage (un calcul immense qui occupe le système). Quand il est fini, une boîte de message apparaît, informant l'utilisateur que le travail est terminé. Mais pendant que le lien était en action, l'utilisateur peut toujours cliquer sur "stop thread" pour arrêter ce lien.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.const
IDM_START_THREAD equ 1
IDM_STOP_THREAD equ 2
IDM_EXIT equ 3
WM_FINISH equ WM_USER+100h

.data
ClassName db "Win32ASMEventClass",0
AppName db "Win32 ASM Event Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0
StopString db "The thread is stopped",0
EventStop BOOL FALSE

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
hMenu HANDLE ?
ThreadID DWORD ?
ExitCode DWORD ?
hEventStart HANDLE ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke CreateEvent,NULL,FALSE,FALSE,NULL
mov hEventStart,eax
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax
.ELSEIF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_START_THREAD
invoke SetEvent,hEventStart
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_ENABLED
.elseif ax==IDM_STOP_THREAD
mov EventStop,TRUE
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSEIF uMsg==WM_FINISH
invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

ThreadProc PROC USES ecx Param:DWORD
invoke WaitForSingleObject,hEventStart,INFINITE
mov ecx,600000000
.WHILE ecx!=0
.if EventStop!=TRUE
add eax,eax
dec ecx
.else
invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
mov EventStop,FALSE
jmp ThreadProc
.endif
.ENDW
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED
jmp ThreadProc
ret
ThreadProc ENDP
end start

Analyse:

Dans cet exemple, je vous montre une autre technique de lien.

.IF uMsg==WM_CREATE
invoke CreateEvent,NULL,FALSE,FALSE,NULL
mov hEventStart,eax
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax

Vous pouvez voir que je crée l'event object ainsi que le lien, pendant le traitement du message WM_CREATE. Je crée l'event object à l'état nonsignalé et avec remis automatique. Après que l'event object soit créé, je crée le lien. Cependant le lien ne se met pas immédiatement au travail parce qu'il attend que l'event object soit mis à l'état 'signalé'. Ci-dessous voici comment le mettre à signalé:

ThreadProc PROC USES ecx Param:DWORD
invoke WaitForSingleObject,hEventStart,INFINITE
mov ecx,600000000

La première ligne de la procédure de lien est l'appel WaitForSingleObject. Ici on attend indéfiniment jusqu'à ce que l'event object passe à l'état signalé avant qu'il ne retourne. Ça signifie que même après que le lien ait été créé, nous le mettons dans un état d'inactivité.
Quand l'utilisateur choisit la commande du menu "run thread", nous mettons l'event object dans l'état signalé, comme indiqué ci-dessous :

.if ax==IDM_START_THREAD
invoke SetEvent,hEventStart

L'appel SetEvent met l'event object à l'état signalé, ce qui fait qu'à son tour l'appel WaitForSingleObject nous dirige vers la procédure du lien et le lien commence à travailler. Quand l'utilisateur choisit la commande "stop thread", nous mettons la valeur de la variable globale "EventStop" à TRUE.

.if EventStop==FALSE
add eax,eax
dec ecx
.else
invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
mov EventStop,FALSE
jmp ThreadProc
.endif

Ceci stop le lien et saute de nouveau à l'appel WaitForSingleObject. Remarquez que nous n'avons pas besoin de remettre manuellement l'event object dans l'état nonsignalé puisque nous avons mis le paramètre bManualReset à FALSE (dans l'appel de CreateEvent).

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32asm tutorial 17: Dynamic Link Libraries

Tutorial 17: Les DLL
(Dynamic Link Libraries)
Bibliothèques de Liaison, Dynamiques

Dans ce Tutorial, nous allons voir les DLLs, ce qu'elles sont et comment les créer.
Vous pouvez télécharger l'exemple ici.

Théorie:

Si vous programmez depuis un petit moment déjà, vous constaterez que les programmes que vous avez l'habitude d'écrire ont quelques routines de code en communs. C'est vraiment une perte de temps de les récrire à chaque fois que vous commencez à développer de nouveaux programmes. Revenons en arrière, aux vieux jours du DOS, les programmeurs ont faits en sorte de stocker les routines les plus généralement employées dans une ou plusieurs bibliothèques. Quand ils veulent utiliser les fonctions, ils ont juste à lier la bibliothèque avec le fichier d'objet, et le linker extrait les fonctions de la bibliothèque et les insère dans le fichier final exécutable. Ce processus est appelé la jonction statique. Les bibliothèques 'Runtime du C' sont de bons exemples. L'inconvénient de cette méthode c'est que vous avez des fonctions identiques dans chaque programme qui les appelle. Votre espace disque est gaspillé stockant plusieurs copies identiques des fonctions. Mais pour les programmes sous DOS, cette méthode est tout à fait valable puisqu'il n'y a généralement qu'un seul programme en action dans la mémoire. Ainsi il n'y a aucune perte précieuse de mémoire.
Sous Windows, la situation devient beaucoup plus critique parce que vous pouvez avoir plusieurs programmes tournant simultanément. La mémoire sera rapidement saturée si votre programme est vraiment grand. Windows a une solution pour ce type de problème : les bibliothèques de liaison dynamiques. Une bibliothèque de liaison dynamique est une sorte de regroupement commun de plusieurs fonctions. Windows ne chargera pas en mémoire plusieurs copies d'une dll, même dans le cas où il y a beaucoup de programmes tournant en même temps. Il fera seulement une copie de la DLL que le programme emploie dans la mémoire. Et je dois clarifier un peu ce point. En réalité, tous les process qui emploient la même dll, auront leurs propres copies de cette dll. C'est comme si il y avait plusieurs copies de cette dll en mémoire. Mais en réalité, Windows rend ça magique avec la pagination et tous les morceaux de process ayant le même code dll. Ainsi en mémoire physique, il y a uniquement une copie du code de la dll. Cependant, chaque process aura sa propre section de données unique de la DLL.
Le programme se lie avec une DLL pendant l'exécution, à la différence de la vieille bibliothèque statique. C'est pourquoi on appelle la bibliothèque de liaison dynamique. Vous pouvez aussi vous défaire d'une DLL pendant l'exécution quand vous n'en avez plus besoin. Si ce programme est le seul à employer la DLL, il la virera immédiatement de la mémoire. Mais si la DLL est toujours employée par d'autres programmes, la DLL reste en mémoire tant que le dernier programme qui l'emploie s'en défasse.
Cependant, le linker a un travail plus difficile quand il exécute l'adresse fixups pour le fichier final exécutable. Puisqu'on ne peut pas "extraire" les fonctions et les insérer complètement dans le fichier final exécutable, d'une façon ou d'une autre on doit stocker assez d'informations sur la DLL et ses fonctions dans le fichier executable final pour être capable de charger correctement la DLL pendant l'exécution.
C'est là ! que la bibliothèque d'importation entre en jeu. Une bibliothèque d'importation contient des informations à propos de la DLL qu'il représente. Le linker peut extraire des bibliothèques d'importation les renseignements dont il a besoin. Quand le loader de Windows charge le programme en mémoire, il voit que le programme se lie avec une DLL donc il essai de trouver cette DLL et la réécrit alors dans l'espace d'adresse du process(du programme), puis exécute les adresses hors du programme pour les fonctions Call qui appellent la DLL.
Vous pouvez vouloir charger la DLL vous-même sans compter sur le loader (chargeur) de Windows. Cette méthode a son pour et son contre :
  • S'il n'y a pas besoin d'une bibliothèque d'importation alors vous pouvez charger et employer n'importe qu'elle DLL même si elle n'est accompagnée d'aucune bibliothèque d'importation. Cependant, vous avez quand même besoin de savoir certaines choses sur les fonctions qu'elle vous propose, des choses comme combien de paramètres elles utilisent…
  • Si vous laissez le loader charger la DLL pour votre programme, et que le loader ne trouve pas cette DLL il annoncera "Le fichier .DLL exigé, xxxxx.DLL est manquant" et paf! Votre programme n'a aucune chance d'être lancé même si cette DLL n'est pas essentiel pour son fonctionnement minimal. Si vous chargez la DLL vous-même, et que la DLL ne peut pas être trouvé et que ce n'est pas essentiel pour l'opération, votre programme le fait remarquer à l'utilisateur mais peut continuer.
  • Vous pouvez utiliser et appeler d'autres fonctions *non décrites dans ces Tuts* qui ne sont pas incluses dans les bibliothèques d'importation. À condition que vous connaissiez assez de renseignements sur ces fonctions.
  • Si vous utilisez LoadLibrary, vous devez appeler GetProcAddress pour chaque fonction que vous souhaitez appeler. GetProcAddress retrouve l'adresse de l'entrypoint d'une fonction à l'intérieur d'une DLL particulière. (l'addresse où commence une fonction particulière). Donc il se peut que votre code soit un peu plus grand et plus lent, mais ça change pas grand chose.
En comparant les avantages / inconvénients d'un appel à LoadLibrary, nous entrons maintenant dans les détails pour créer une DLL.
Le code suivant est le squelette de la DLL. (DLL skeleton.)

;--------------------------------------------------------------------------------------
; DLLSkeleton.asm
;--------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp
;---------------------------------------------------------------------------------------------------
; C'est une fonction factice
; Ça ne fait rien. Je l'ai mise ici pour montrer où vous pouvez insérer des fonctions à l'intérieur d'une DLL.
;----------------------------------------------------------------------------------------------------
TestFunction proc
ret
TestFunction endp

End DllEntry

;-------------------------------------------------------------------------------------
; DLLSkeleton.def
;-------------------------------------------------------------------------------------
LIBRARY DLLSkeleton
EXPORTS TestFunction

Ce tout petit programme ci-dessus est le squelette de la DLL. Chaque DLL doit avoir une fonction d'entrypoint. Windows appellera la fonction d'entrypoint chaque fois que :

  • La DLL est loadée en premier
  • La DLL est unloadée (libérée)
  • Un lien est créé dans ce même process
  • Un lien est détruit dans ce même process
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp

Vous pouvez nommer la fonction d'entrypoint comme vous le souhaitez tant que vous avez une FIN correspondante à 'END ' . Cette fonction prend trois paramètres, dont seulement les deux premier sont importants.
hInstDLL est l'handle du module(de la fonction) de la DLL. Ce n'est pas le même que l'instance handle du process. Vous devez garder cette valeur si vous avez besoin de l'employer plus tard. Vous ne pourrez pas l'obtenir de nouveau aussi facilement. Exemple: Si vous connaissez la DLL Kernel32.DLL vous savez bien qu'elle possède plusieurs fonctions ou modules à l'intérieurs d'elle, dont GetCommandeLineA et ExitProcess.
reason peut être une des quatre valeurs :

  • DLL_PROCESS_ATTACH La DLL reçoit cette valeur quand elle est injectée la toute première fois dans l'espace d'adresses du process. Vous pouvez utiliser cette occasion pour faire l'initialisation.
  • DLL_PROCESS_DETACH La DLL reçoit cette valeur quand elle est unloadée (supprimée) de l'espace d'adresses du process. Vous pouvez employer cette occasion pour faire un peu de nettoyage comme désinffecter la mémoire etc…
  • DLL_THREAD_ATTACH La DLL reçoit cette valeur quand le process crée un nouveau lien.
  • DLL_THREAD_DETACH La DLL reçoit cette valeur quand un lien dans le process est détruit.
Renvoyez TRUE dans eax si vous souhaitez que la DLL continue son exécution. Si vous renvoyez FALSE, la DLL ne sera pas chargée. Par exemple, si votre code d'initialisation doit réserver de la mémoire et qu'il ne peut pas faire ça avec succès, la fonction d'entrypoint doit renvoyer FALSE pour indiquer que la DLL ne peut pas être lancée.
Vous pouvez placer vos fonctions dans la DLL après la fonction Entrypoint ou même avant çà. Mais si vous voulez qu'elles soient CALLables par d'autres programmes, vous devez mettre leurs noms dans la liste d'exportation du fichier de définition de module (.def).
Une DLL a besoin d'un fichier de définition de module dans son process liée à son développement. Nous y jetons un coup d'œil maintenant.

LIBRARY DLLSkeleton
EXPORTS TestFunction

Normalement vous devez avoir la première ligne. La déclaration LIBRARY définit le nom du module interne (la fonction souhaitée) de la DLL. Vous devez correspondre avec ce module grâce au nom du fichier de la DLL.
La déclaration EXPORTS dit au linker quelles fonctions sont en train de tourner dans la DLL, c'est-à-dire quelles fonctions sont déjà utilisées par d'autres programmes. Dans l'exemple, nous voulons que d'autres modules soient capables d'appeler TestFunction, donc nous mettons son nom dans la déclaration EXPORTS.
Un autre changement c'est le commutateur du linker. Vous devez mettre le commutateut /DLL et /DEF:<votre nom de fichier def> dans votre linker de commutation comme cela :

link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:\masm32\lib DLLSkeleton.obj

Les commutateurs d'assembleur sont les mêmes, à savoir /c /coff /Cp. Ainsi après que vous ayez lié le fichier objet, vous obtiendrez *.DLL et *.lib. Le *.lib est la bibliothèque d'importation que vous pouvez employer pour vous lier avec d'autres programmes qui emploient les fonctions de votre DLL.
Ensuite je vous montrerai comment employer LoadLibrary pour charger une DLL.

;---------------------------------------------------------------------------------------------
; UseDLL.asm
;----------------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

.data
LibName db "DLLSkeleton.dll",0
FunctionName db "TestHello",0
DllNotFound db "Cannot load library",0
AppName db "Load Library",0
FunctionNotFound db "TestHello function not found",0

.data?
hLib dd ? ; l' handle de la librairie (l' handle de la DLL)
TestHelloAddr dd ? ; l'adresse de la function TestHello

.code
start:
invoke LoadLibrary,addr LibName
;---------------------------------------------------------------------------------------------------------
; Appelle LoadLibrary avec le nom de la DLL désirée. Si l'appel est couronné de succès
; Il renverra l'handle de la bibliothèque (DLL). Sinon, il renverra le NULL.
; Vous pouvez passer l'handle de la bibliothèque à GetProcAddress ou n'importe quelle fonction qui a besoin
; de l'handle d'une DLL comme paramètre.
;------------------------------------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK
.else
mov hLib,eax
invoke GetProcAddress,hLib,addr FunctionName
;-------------------------------------------------------------------------------------------------------------
; Quand vous obtenez l'handle de la bibliothèque, vous le passez à GetProcAddress avec l'adresse
; du nom de la fonction de la DLL que vous souhaitez appeler. Ceci renvoie l'adresse
; de la fonction si tout s'est passé avec succès. Autrement, il renvoie le NULL.
; Les adresses des fonctions ne changent pas à moins que vous ne déchargiez puis rechargiez la bibliothèque.
; Donc vous pouvez les placer dans des variables globales pour de futures utilisations.
;-------------------------------------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK
.else
mov TestHelloAddr,eax
call [TestHelloAddr]
;-------------------------------------------------------------------------------------------------------------
; Ensuite, vous pouvez appeler la fonction avec un appel simple avec la variable contenant
; l'adresse de la fonction comme opérande.
;-------------------------------------------------------------------------------------------------------------
.endif
invoke FreeLibrary,hLib
;-------------------------------------------------------------------------------------------------------------
; Quand vous n'avez plus besoin de la bibliothèque, vous la déchargez avec FreeLibrary.
;-------------------------------------------------------------------------------------------------------------
.endif
invoke ExitProcess,NULL
end start

Donc vous pouvez voir que l'utilisation de LoadLibrary est un peu plus compliquée mais elle est aussi plus flexible.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion Win32asm Tutorial 18: Common Controls

Tutorial 18: Les Controls Communs
(Common Controls)

Nous allons voir ce que sont les Controls les plus utilisées et voir comment on les emploie. Ce Tutorial sera seulement une rapide introduction à ceux-ci.
Downloadez l'exemple ici.

Théorie:

Windows 95 a été doté de plusieurs nouvelles interfaces utilisateur que n'avait pas Windows 3.1x. Ceci enrichi encore le GUI (NdT : Graphic User Interface). Plusieurs d'entre eux sont largement employés avant même que Windows 95 ne s'affiche à l'écran, comme la barre de statut, des barres d'outils etc. Sous Windows 3.x, les programmeurs devaient les coder eux-mêmes. Maintenant Microsoft les a inclus dans Windows 9x et NT. Nous allons les voir ici.
Voici ces nouveaux Controls :
  • Toolbar -----> (barre d'outils)
  • Tooltip -----> (Bulle d'information: voir tuorial 27)
  • Status bar -----> (barre d'état)
  • Property sheet -----> (feuille de propriété)
  • Property page -----> (page de propriété)
  • Tree view -----> (dossiers organisés sous la forme d'un arbre. Voir tutorial 19)
  • List view -----> (dossiers organisés sous la forme d'une simple liste. Voir tutorial 31)
  • Animation -----> (animation, films AVI)
  • Drag list -----> ()
  • Header -----> (en-tete)
  • Hot-key -----> (fontion touches racourcis)
  • Image list -----> (liste d'images)
  • Progress bar -----> (barre de progression de chargement)
  • Right edit -----> ()
  • Tab -----> (la touche Tab)
  • Trackbar -----> ()
  • Up-down -----> (les flèches haut/bas pour le défilement de d'une page)
Voici une adresse ou vous trouverez de petits programmes d'exemples de chacun de ces Common Control. Ils ont étés codés en C++ et non pas en MASM mais le principe est là. Vous comprendrez ce que chacun d'eux représente.
http://www.microsoft.com/msj/defaulttop.asp?page=/msj/0798/controlspytop.htm
(Merci à Sharpsighted pour l'info. sharpsighted@caramail.com)

Puisque il y en a beaucoup, le fait de les charger en mémoire et les déclarer serait une perte de ressource. Tous, à l'exception du contrôle d'édition, sont stockés dans comctl32.dll. Et n'importe quel programme peut les charger quand il a besoin d'employer un de ces Control. Le contrôle d'édition (riche ou évolué)réside dans son propre dll, richedXX.dll, parce qu'il est très compliqué et plus volumineux que ses frères.
Vous pouvez charger comctl32.dll grâce à l'inclusion d'un Call InitCommonControls dans votre programme. InitCommonControls est une fonction de comctl32.dll, donc en vous y référant n'importe où dans votre code, fera que le Loader PE chargera comctl32.dll quand votre programme est en court. Vous ne devez pas l'exécuter, incluez la simplement quelque part dans votre code. Cette fonction ne fait RIEN! Sa seule et unique instruction est "ret". Son seul but c'est d'inclure la référence de comctl32.dll dans la section d'importation pour que le Loader PE le charge à chaque fois que le programme est chargé. Le vrai boulot se fait au niveau de l'entrypoint de la fonction DLL qui enregistre chacune des classes des différents Common Controls (common control classes) quand la DLL est chargée. Les 'Commons Controls' sont créés sur la base de ces classes, tout comme d'autre 'child window controls' tels que la zone d'édition, la liste etc.
Pour le contrôle d'édition évolué (riche) c'est une autre histoire. Si vous voulez l'employer, vous devez appeler LoadLibrary pour le charger et appeler FreeLibrary pour le décharger.
Maintenant nous allons voir comment les créer. Vous pouvez employer un éditeur de ressources pour les incorporer dans des boîtes de dialogue ou bien vous pouvez les créer vous-même. Presque tous les common controls sont créés en appelant CreateWindowEx ou CreateWindow, en leur passant le nom de la classe du contrôle. Quelques common controls ont des fonctions de création spéciales, cependant, ce ne sont que des broutilles autour de CreateWindowEx pour rendre la création de ces 'controls' plus simple. Ces fonctions spécifiques de création sont inscrites ci-dessous :
  • CreateToolbarEx
  • CreateStatusWindow
  • CreatePropertySheetPage
  • PropertySheet
  • ImageList_Create
Pour pouvoir créer ces 'common controls', vous devez connaître leurs noms de classe. Les voici inscrits ci-dessous :
Class Name | Common Control
ToolbarWindow32 | Toolbar
tooltips_class32 | Tooltip
msctls_statusbar32 | Status bar
SysTreeView32 | Tree view
SysListView32 | List view
SysAnimate32 | Animation
SysHeader32 | Header
msctls_hotkey32 | Hot-key
msctls_progress32 | Progress bar
RICHEDIT | Rich edit
msctls_updown32 | Up-down
SysTabControl32 | Tab
Les 'Property sheets', les 'property pages' et l 'image list control' (soit les feuilles de propriété, les pages de propriété, et le contrôle de liste d'image) ont leurs propres fonctions spécifiques de création. La 'Drag list control' gonfle encore la liste donc elle n'a pas sa propre classe. Ces susdits noms de classe sont vérifiés en regardant le scénario de ressource, produit par l'éditeur de ressource Visual C++. Ils diffèrent des noms de classe inscrits par win32 de la référence Borland api et de ceux de la Programmation Windows95 de Charles Petzold. La liste est précisément celle-ci.
Ces common controls peuvent utiliser les styles généraux de fenêtres tel que WS_CHILD etc. Ils ont aussi leurs propres styles spécifiques comme TVS_XXXXX pour la 'tree view control' (contrôle montrant les fichiers sous forme d'arbre ), LVS_xxxx pour la 'list view control' (contrôle montrant les fichiers sous forme d'une liste ), etc. La référence d' api Win32 sera votre meilleur ami à cet égard.
Maintenant que nous savons comment créer des common controls, nous pouvons nous intéresser à la méthode de communication entre ces common controls et leurs parents. À la différence des 'child window controls', les 'common controls' ne communiquent pas avec leur parent grâce à des messages WM_COMMAND. Au lieu de ça, ils envoient des messages WM_NOTIFY à la fenêtre parente lorsque certains événements bien précis se produisent. Le parent peut contrôler les enfants en leur envoyant des messages. Il y a aussi beaucoup de nouveaux messages pour ces nouveaux Controls. Vous devez consulter votre référence api win32 pour plus de détail.
Dans l'exemple suivant, on va examiner les deux Controls suivants, la barre de progression et la barre de statut. ('progress bar' et 'status bar controls')

échantillon de code:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.const
IDC_PROGRESS equ 1 ; Les n° d'Identités des 'controls' (par exemple n°ID de la barre de progression)
IDC_STATUS equ 2
IDC_TIMER equ 3

.data
ClassName db "CommonControlWinClass",0
AppName db "Common Control Demo",0
ProgressClass db "msctls_progress32",0 ; La 'class name' de la barre de progression
Message db "Finished!",0
TimerID dd 0

.data?
hInstance HINSTANCE ?
hwndProgress dd ?
hwndStatus dd ?
CurrentStep dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
invoke InitCommonControls

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\
WS_CHILD+WS_VISIBLE,100,\
200,300,20,hWnd,IDC_PROGRESS,\
hInstance,NULL
mov hwndProgress,eax
mov eax,1000 ; Le paramètre lParam du message PBM_SETRANGE contient la gamme, (en général de 0 à 100)
mov CurrentStep,eax
shl eax,16 ; La borne haute de la gamme (ici 100) est dans le mot de poids fort
invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax
invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0
invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUS
mov hwndStatus,eax
invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; crée un minuteur (un timer)
mov TimerID,eax
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.if TimerID!=0
invoke KillTimer,hWnd,TimerID
.endif
.elseif uMsg==WM_TIMER ; Quand un 'timer event' survient
invoke SendMessage,hwndProgress,PBM_STEPIT,0,0 ; Remplissage (pas à pas) de la barre de progression.
sub CurrentStep,10
.if CurrentStep==0
invoke KillTimer,hWnd,TimerID
mov TimerID,0
invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message
invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SendMessage,hwndStatus,SB_SETTEXT,0,0
invoke SendMessage,hwndProgress,PBM_SETPOS,0,0
.endif
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
end start

Analyse:

    invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
    invoke ExitProcess,eax
    invoke InitCommonControls
J'ai délibérément mis InitCommonControls après ExitProcess pour vous faire voir que InitCommonControls est juste ici pour la mettre en référence comctl32.dll, dans la section d'importation. Comme vous pouvez le voir, les 'common controls' fonctionnent parfaitement même lorsque InitCommonControls n'est pas exécuter.
    .if uMsg==WM_CREATE
    invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\
    WS_CHILD+WS_VISIBLE,100,\
    200,300,20,hWnd,IDC_PROGRESS,\
    hInstance,NULL
    mov hwndProgress,eax
Voici l'endroit où nous créons le common control. Notez que cet appel de CreateWindowEx contient la variable 'hWnd' en tant que 'handle de la fenêtre parente'. On spécifie aussi un 'Control ID' pour l'identification de ce contrôle. Cependant, puisque nous avons l' handle du 'control', cet ID n'est pas employé. Tout 'child window control' doit avoir le style WS_CHILD.
    mov eax,1000
    mov CurrentStep,eax
    shl eax,16
    invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax
    invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0
Après que la barre de progression est été créée, nous pouvons définir sa gamme. La gamme par défaut va de 0 à 100. Si vous n'en êtes pas satisfaits, vous pouvez spécifier votre propre gamme avec le message PBM_SETRANGE. Le message 'lParam' contient la gamme, la borne supérieure de la gamme (ici 100d ou 64h) est placée dans le mot de poids fort alors que le minimum (0) est dans le mot de poids faible. Ce qui fait que IParam vaut 00640000h. Que fait Shl eax,16 ? Shl décale 16 bit de eax vers la gauche. Ici on est en octet donc 8 bits, ce qui fait qu'on déplace 16/8=2 octets de eax vers la gauche, ce qui fait que eax prend la valeur 00000064h. On vient de récupérer la borne haute de IParam dans eax.
Vous pouvez spécifier le 'nombre de pas' dont cette barre sera remplie, en employant PBM_SETSTEP. Dans notre exemple, on le met à 10 ce qui signifie que lorsque un message PBM_STEPIT est envoyé à la barre de progression, l'indicateur de progression montera de 10 par 10. Mais vous pouvez aussi définir le niveau de l'indicateur en envoyant des messages PBM_SETPOS. Ce message vous donne un contrôle plus précis sur la barre de progression.
    invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUS
    mov hwndStatus,eax
    invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; Crée un timer
    mov TimerID,eax
Ensuite, nous créons une barre de statut en appelant CreateStatusWindow. C'est un appel facile à comprendre donc je ne ferai pas de remarques. Après que la fenêtre de statut ait été créée, nous fabriquons un minuteur. Dans cet exemple, nous mettrons à jour la barre de progrès dans un intervalle régulier de 100ms, donc nous devons créer un contrôle de minuteur (un control timer). Ci-dessous voici le 'prototype de fonction' de SetTimer.
    SetTimer PROTO hWnd:DWORD, TimerID:DWORD, TimeInterval:DWORD, lpTimerProc:DWORD
hWnd : handle de la fenêtre Parente
TimerID : Le n° d'Identité du minuteur (du Timer), différent de zéro. Vous pouvez mettre le n° que vous voulez pour l'identifier. Mais si vous avez plusieurs Timers , chacun doit avoir son propre n° d'ID
TimerInterval : L'intervalle de temps du minuteur, en millisecondes, doit être terminé avant que le minuteur appelle la procédure du minuteur ou envoie un message WM_TIMER
lpTimerProc : L'adresse de la fonction minuteur (la fonction de temps) qui sera appelée lorsque l'intervalle de temps est expiré. Si ce paramètre est NULL, le minuteur enverra le message WM_TIMER à la fenêtre parente, au lieu de cette adresse.

Si cet appel est couronné de succès, il renverra le TimerID. S'il a échoué, il retourne la valeur 0. C'est pourquoi la valeur du TimerID doit absolument être différente de zéro.

    .elseif uMsg==WM_TIMER
    invoke SendMessage,hwndProgress,PBM_STEPIT,0,0
    sub CurrentStep,10
    .if CurrentStep==0
    invoke KillTimer,hWnd,TimerID
    mov TimerID,0
    invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message
    invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
    invoke SendMessage,hwndStatus,SB_SETTEXT,0,0
    invoke SendMessage,hwndProgress,PBM_SETPOS,0,0
    .endif
Quand l'intervalle de temps indiqué expire, le minuteur (le timer) envoie le message WM_TIMER. Vous pourrez placer votre propre code ici pour qu'il soit exécuté. Dans cet exemple, nous mettons à jour la barre de progression et vérifions ensuite si la limite maximale a été atteinte (dépassée). Si c'est le cas, nous fermons le Timer et mettons ensuite le texte dans la fenêtre de statut avec le message SB_SETTEXT. Une boîte de message est affichée, et quand l'utilisateur clique sur OK, nous purifions le texte de la barre de statut ainsi que la barre de progression.
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 19: Tree View Control

Tutorial 19: Contrôle 'des dossiers organisés sous la forme d'un arbre'

(Tree View Control)

Dans ce Tutorial, nous allons voir ce qu'est un 'tree view control' (C'est exactement de cette façon là que l'Explorateur Windows nous affiche sous forme d'un arbre tous les dossiers de notre disque dur). De plus, nous apprendrons aussi à construire le mécanisme du 'drag and drop' (glisser-déposer) et comment employer une 'image list' (liste d'image).
Downloadez l'exemple ici.

Théorie:

Un 'tree view control' est une sorte de fenêtre spéciale qui affiche des objets en ordre hiérarchique. Le meilleur exemple c'est la fenêtre de gauche de l'Explorateur de Windows. Vous pouvez employer ce contrôle pour montrer les raccordements entre les différents objets (ici des dossiers).
Vous pouvez créer un 'tree view control' en appelant CreateWindowEx, en lui passant la donnée "SysTreeView32" comme nom de classe ou bien vous pouvez l'incorporer dans une boîte de dialogue. N'oubliez pas de placer un Call InitCommonControls dans votre code.
Il y a plusieurs styles différents de 'tree view control'. Ci-dessous voici les trois plus employés.
    TVS_HASBUTTONS sert à mettre les petits boutons carrés 'plus (+) et moins (-)' à côté des l'Items parentaux (à côté des dossiers par exemple). L'utilisateur clique sur un de ces bouton associés à un dossier parent pour qu'il se déploie et affiche ses sous dossiers ou se recroqueville sur lui-même. Pour pouvoir inclure des boutons au niveau de la racine des Items de l'arbre, on doit également spécifié TVS_LINESATROOT.
    TVS_HASLINES emploie des lignes pour montrer la hiérarchie entre les différents Items.
    TVS_LINESATROOT emploie des lignes pour lier des Items à la racine du 'tree-view control'. Cette valeur est ignorée si TVS_HASLINES n'est pas également spécifié.
Le 'tree view control', comme d'autre common controls, communique avec la fenêtre parente via des messages. La fenêtre parente peut lui envoyer divers messages et le 'tree view control' peut envoyer des messages "d'avis" à sa fenêtre parente. À cet égard, le 'tree view control' n'est pas différent des fenêtres habituelles.
Quand quelque chose d'intéressant lui arrive, il envoie un message WM_NOTIFY à la fenêtre parente en l'accompagnant de l'information.
    WM_NOTIFY
    WParam est le n°d'IDentité du Contrôle (Control ID), on ne garantit pas que cette valeur soit unique donc nous ne
    l'emploierons pas. Au lieu de ça, nous employons hwndFrom ou le membre IDFrom de la structure
    NMHDR pointé par lParam

    LParam est le pointer qui repère la structure NMHDR. Certaines commandes peuvent passer un pointer de plus
    grande taille. Mais il doit avoir une structure NMHDR dans son premier membre.
    C'est-à-dire qu'au moins pour lParam, vous devez être sûrs qu'il indique une structure NMHDR.
Maintenant examinons la structure NMHDR.
    NMHDR struct DWORD
    hwndFrom DWORD ?
    idFrom DWORD ?
    code DWORD ?
    NMHDR ends
HwndFrom est l'handle du contrôle qui envoie ce message WM_NOTIFY.
IdFrom est le contrôle ID du contrôle qui envoie ce message WM_NOTIFY.
Code est le message réel que le contrôle veut envoyer à la fenêtre parente.
Les réponses du 'Tree View' sont celles avec TVN_ au début du nom. Les messages du Tree View sont ceux avec TVM _, tels que TVM_CREATEDRAGIMAGE. Le 'tree view control' envoie TVN_XXXX dans le membre de code du NMHDR. La fenêtre parente peut envoyer TVM_XXXX pour le contrôler.

Ajout d'Item au tree view control

Après que vous l'ayez créé, vous pouvez même rajouter des Item (des articles) au tree view control. Vous pouvez faire ça en employant le message TVM_INSERTITEM.
    TVM_INSERTITEM
    wParam = 0;
    lParam = est le pointeur qui (pointe) sur TV_INSERTSTRUCT;
A ce niveau, on doit connaître quelques petits trucs à propos des Items (articles) du tree view control.
Un Item peut être lui-même parent, enfant (un child) , ou les deux en même temps. Un Item parent est un Item qui a d'autres sous-Items associés qui dépendent de lui. En même temps, cet Item parent peut aussi être l'enfant d'un autre Item. Un Item sans un parent est appelé un Item racine (par exemple : dans l'Explorateur de Windows, l'item 'Windows' est un item parent alors que l'Item 'System' est un de ses items enfant). Il peut y avoir plusieurs d'Items racine dans un 'tree view control'. Maintenant examinons la structure TV_INSERTSTRUCT.
    TV_INSERTSTRUCT STRUCT DWORD
    hParent DWORD ?
    hInsertAfter DWORD ?
    ITEMTYPE <>
    TV_INSERTSTRUCT ENDS
hParent est l'handle de l'Item parent. Si ce membre est la valeur TVI_ROOT ou NULL, l'Item est inséré à la racine du 'tree view control'.
hInsertAfter est l'handle d'un prochain Item, qu'il soit un 'Child Item' ou bien un nouvel 'Item Parent':
  • TVI_FIRST = > Insère un Item au début de la liste.
  • TVI_LAST = > Insère un Item en fin de liste.
  • TVI_SORT = > Insère un Item dans la liste dans l'ordre alphabétique.
    ITEMTYPE UNION
    itemex TVITEMEX <>
    item TVITEM <>
    ITEMTYPE ENDS
Nous emploierons uniquement TV_ITEM ici.
    TV_ITEM STRUCT DWORD
    imask DWORD ?
    hItem DWORD ?
    state DWORD ?
    stateMask DWORD ?
    pszText DWORD ?
    cchTextMax DWORD ?
    iImage DWORD ?
    iSelectedImage DWORD ?
    cChildren DWORD ?
    lParam DWORD ?
    TV_ITEM ENDS
Cette structure est employée pour envoyer et recevoir des renseignements (des messages) d'un Item du Tree View. Par exemple, avec TVM_INSERTITEM, il est employé pour définir l'attribut de l'Item qui doit être inséré dans le tree view control. Avec TVM_GETITEM, on envoie l'information pour choisir le type de Tree View Item.
imask est employé pour spécifier quel(s) membre(s) de la structure TV_ITEM est (sont) valide(s). Par exemple, si la valeur dans Imask est TVIF_TEXT, ça signifie qu'uniquement le membre pszText est valide. Vous pouvez combiner plusieurs flags ensembles.
hItem est l'handle de l'Item du Tree View. Chaque item a son propre handle. Lorsque vous souhaitez manipuler ou utiliser un item, vous devez le choisir grâce à son handle.
pszText est un pointeur qui (pointe) sur une chaîne de caractères terminée par un caractère nul. Cette chaîne de caractères est le label de l'Item du Tree View (Le texte associé à cet Item).
cchTextMax est uniquement employé lorsque vous voulez retrouver le label de l'Item du Tree View. Puisque vous pointer sur le buffer de pszText (on pointe sur la zone mémoire où est écrit le texte), Windows doit connaître la taille de ce buffer. Ce membre (cchTextMax) sert donc à donner la taille du buffer.
iImage et iSelectedImage font référence à l'index d'une 'image list' laquelle contient des images qui doivent être affichées lorsque l'Item n'est pas choisi (iImage) et quand il est choisi (iSelectedImage). Si vous vous souvenez de la fenêtre de gauche de l'Explorateur Windows, les images des dossiers sont contrôlées par ces deux membres.
Afin d'insérer un Item dans un tree view control, vous devez au moins mettre des valeurs dans hParent, hInsertAfter et vous devez remplir imask ainsi que le membre pszText.

Addition d'images à un tree view control :

Si vous souhaitez placer une image à gauche du label (du texte) d'un 'Tree View Item', vous devez créer une 'image list' et l'associer au 'tree view control'. Vous pouvez créer une 'image list' en appelant ImageList_Create.
    ImageList_Create PROTO cx:DWORD, cy:DWORD, flags:DWORD, \
    cInitial:DWORD, cGrow:DWORD
Cette fonction ,si elle se déroule avec succès, renvoie l'handle d'une 'image list' vide.
cx représente la largeur (en pixels) de chaque image dans cet 'image list'.
cy est la hauteur de chaque image (en pixels) de cet 'image list'. Toutes les images de l' 'image list' doivent être égales en taille. Si vous définissez un grand bitmap (image), Windows emploiera cx et cy pour la *découper* en plusieurs morceaux. Donc vous devez préparer votre propre image comme une bande d'images avec des dimensions identiques.
flags détermine le type d'images contenues dans cet 'image list', si elles sont colorées ou monochromes et leur profondeur de colorations (16millions de couleurs ou moins). Consultez votre référence win32 api pour de plus amples détails.
cInitial est le nombre d'images que cet 'image list' contiendra au commencement. Windows emploiera ces renseignements pour réserver de la mémoire pour ces images.
cGrow est le nombre d'images dont l'image list peut être agrandit lorsque le système a besoin de retaillée la liste de départ pour faire place pour de nouvelles images. Ce paramètre représente le nombre de nouvelles images que l'image list agrandit peut contenir.
L' image list n'est pas une fenêtre! C'est seulement une sorte d'entrepos d'images pour que d'autres fenêtres puisse s'en servir.
Après que l'image list soit créé, vous pouvez y ajouter des images en appelant ImageList_Add
    ImageList_Add PROTO himl:DWORD, hbmImage:DWORD, hbmMask:DWORD
Cette fonction renvoie la valeur -1 si elle échoue.
himl est l'handle de l'image list dans laquelle vous souhaitez ajouter d'autres images. Cette la valeur obtenue après le succès d'un appel à ImageList_Create
hbmImage est l'handle de l'image qui doit être rajoutée à l' 'image list'. D'habitude, on stocke les images en tant que ressources et on les charge grâce à l'appel LoadBitmap. Remarquez que vous n'avez pas à spécifier le nombre de morceaux d'images contenues dans cette image (entière) parce que cette information est représentée par les paramètres cx et cy, lesquels sont déduits lors de leur passage dans l'appel ImageList_Create.
hbmMask est l'handle du bitmap (l'image en entier) qui contient le masque. Si aucun masque n'est employé avec l'image list, ce paramètre est ignoré.
Normalement, nous ajoutons seulement deux images à une image list pour les utiliser avec le tree view control : celles-ci sont employées soit quand l'Item du Tree View n'est pas sélectionné soit l'autre quand l'Item est sélectionné.
Quand l' image list est prête, vous l'associez avec le tree view control en envoyant TVM_SETIMAGELIST au tree view control.
    TVM_SETIMAGELIST
    WParam = type d'image list à afficher. Il y a deux choix :
    • TVSIL_NORMAL est le Jeu normal d' image list, qui contient les images choisies et non choisies pour l'Item du Tree View.
    • TVSIL_STATE est le Jeu 'd'état' d' image list, qui contient les images d'Item de Tree View qui sont dans un état défini par l'utilisateur.
    lParam = handle de l' image list

Recherche des renseignements a propos des items d'un tree view.

Vous pouvez retrouver l'information d'un Item du Tree View en envoyant le message TVM_GETITEM.
    TVM_GETITEM
    wParam = 0
    lParam = pointer sur la structure TV_ITEM pour qu'on puisse la remplir avec l'information.
Avant que vous n'envoyiez ce message, vous devez remplir imask avec le flag qui dit quel(s) sorte(s) de TV_ITEM est ce que vous souhaitez que Windows prenne. Et plus important, vous devez remplir hItem avec l'handle de l'Item dont vous voulez avoir l'information. Et ça pose un problème : Comment peut-on connaître l'handle de l'Item dont on souhaite justement retrouver des renseignements? Doit-on stocker tous les handles du Tree View?
La réponse est très simple : faut pas faire ça. On peut utiliser le message TVM_GETNEXTITEM du tree view control pour retrouver l'handle de l'Item dont on a spécifié l'attribut(s). Par exemple, on peut vérifier l'handle du premier Child Item, de l'Item racine, ou de l'Item choisi, et cetera.
    TVM_GETNEXTITEM
    wParam = flag
    lParam = handle d'un Item du Tree View (seulement nécessaire pour certaines valeurs de flag)
La valeur dans wParam est très importante donc je met tous les flags ci-dessous :
    • TVGN_CARET Retrouve l'Item actuellement choisi.
    • TVGN_CHILD Retrouve le premier Child Item appartenant à l'Item indiqué par le paramètre hitem.
    • TVGN_DROPHILITE Retrouve l'Item qui est la cible d'une opération de glisser-déplacer. (Drad and Drop)
    • TVGN_FIRSTVISIBLE Retrouve le premier Item visible.
    • TVGN_NEXT Retrouve le Child Item suivant (de mêmes parents).
    • TVGN_NEXTVISIBLE Retrouve l'Item visible qui suit l'Item indiqué. L'Item indiqué doit être visible. Utilisez le message TVM_GETITEMRECT pour déterminer si un Item est visible.
    • TVGN_PARENT Retrouve le parent de l'Item indiqué.
    • TVGN_PREVIOUS Retrouve le Child Item précédent (de mêmes parents).
    • TVGN_PREVIOUSVISIBLE Retrouve le premier Item visible qui précède l'Item indiqué. L'Item indiqué doit être visible. Employez le message TVM_GETITEMRECT pour déterminer si un Item est visible.
    • TVGN_ROOT Retrouve le topmost (ex: C:) ou le tout premier Item du tree-view control.
Vous pouvez voir que, vous pouvez retrouver l'handle de n'importe quel Item du Tree View en vous intéressant à ces messages. SendMessage renvoie l'handle de l'Item du Tree View si la tache est couronnée de succès. Vous pouvez alors remplir le membre hItem (appartenant à TV_ITEM) avec la valeur de l'handle renvoyé pour ensuite pouvoir l'utiliser avec le message TVM_GETITEM.

Opération Drag and Drop (glisser-déposer) dans un tree view control

Cette partie est la raison pour laquelle j'ai décidé d'écrire ce Tutorial. Quand j'ai essayé de suivre l'exemple de la référence win32 api (le win32.hlp d'InPrise), j'ai vraiment été frustré parce qu'en fait il manque l'information essentielle. D'essais en essais et d'erreurs en erreurs, j'ai fini par comprendre comment utiliser le système de 'Drag and Drap' d'un tree view control et je ne veux pas que d'autres personnes aient à refaire le même chemin de croix que moi.
Ci-dessous voici les étapes pour accomplir une opération Drag&Drop dans un tree view control.
  1. Quand l'utilisateur essaye de déplacer un Item, le tree view control envoie le message TVN_BEGINDRAG à la fenêtre parente. Vous pouvez profiter de cette occasion pour créer une image de traînée qui est l'image qui sera employée pour représenter l'Item quand on le déplace. Vous pouvez envoyer TVM_CREATEDRAGIMAGE au tree view control pour lui dire de créer une 'image de traînée' par défaut de l'image qui est actuellement employée par l'Item que l'on déplace. Le tree view control créera une image list avec uniquement l 'image de traînée' et vous renverra l'handle de cette image list.
  2. Après que l'image de traînée aie été créée, vous définissez le point chaud de l'image de traînée en appelant ImageList_BeginDrag.
      3. ImageList_BeginDrag PROTO himlTrack:DWORD, \
      iTrack:DWORD , \
      dxHotspot:DWORD, \
      dyHotspot:DWORD
      himlTrack est l'handle de l'image list qui contient l'image de traînée.
      iTrack est l'index qui indique l'image de traînée dans l'image list.
      dxHotspot indique la distance relative du point chaud (de l'image de traînée)dans le plan horizontal. Puisque cette image sera employée à la place du curseur de la souris, nous avons donc besoin de définir où se situe le point chaud dans l'image.
      dyHotspot indique la distance relative du point chaud dans le plan vertical.
      Normalement, iTrack sera mis à 0 si vous dites au tree view control de créer une image de traînée à votre place. Et dxHotspot et dyHotspot peuvent être égales à 0 si vous voulez placer le point chaud au niveau du coin supérieur gauche de l'image de traînée.
  3. Quand l'image de traînée est prête à être affichée, nous appelons ImageList_DragEnter pour la faire apparaître dans la fenêtre.
      4. ImageList_DragEnter PROTO hwndLock:DWORD, x:DWORD, y:DWORD
      hwndLock est l'handle de la fenêtre qui affiche l'image de traînée. Cette image n'aura pas la possibilité de se déplacer à l'extérieur de cette fenêtre.
      x et y sont les coordonnées de l'endroit où l'image de traînée doit être affichée au commencement. Remarquez que ces valeurs on pour référence (0;0) le coin supérieur gauche de la fenêtre, et non pas son secteur client.
  4. Maintenant que l'image de traînée est affichée dans la fenêtre, vous devrez maintenir l'opération (DRAG) de traînée dans le tree view control. Cependant, il y a un petit problème ici. Nous devons contrôler le chemin de la traînée avec WM_MOUSEMOVE et l'emplacement où on dépose (DROP) avec le message WM_LBUTTONUP. Cependant, si l'image de traînée appartient à une autre 'Child Windows' (donc pas à notre fenêtre principale [ou parente]), la fenêtre parente ne recevra jamais le message de la souris. La solution est de capturer l'entrée souris avec SetCapture. En employant l'appel, les messages souris seront dirigés vers la fenêtre indiquée indépendamment de l'endroit où se trouve le curseur de la souris (et ici le curseur de la souris vient d'être momentanément remplacé par l'image de traînée).
  5. A l'intérieur de WM_MOUSEMOVE, vous mettrez à jour le chemin de la traînée avec l'appel de ImageList_DragMove. Cette fonction déplace l'image que l'on traîne pendant une opération de glisser-déplacer. En outre, si c'est votre souhait, vous pouvez contrôler l'Item de l'image de traînée en envoyant TVM_HITTEST pour vérifier si l'image de traînée passe au dessus d'un autre Item. Si c'est le cas, vous pouvez envoyer le message TVM_SELECTITEM avec le flag rendu par TVGN_DROPHILITE. Remarquez qu'avant l'envoi du message TVM_SELECTITEM, vous devez d'abord cacher l'image de traînée sinon votre image laissera de vilaines traces en se déplaçant. Vous pouvez cacher l'image de traînée en appelant ImageList_DragShowNolock et, après que l'opération soit finie, appelez ImageList_DragShowNolock de nouveau pour réafficher l'image de traînée.
  6. Quand l'utilisateur relâche le bouton gauche de la souris, vous devez faire plusieurs choses. Si vous perdez le contôle de votre Item, vous deviez le réactiver en envoyant le message TVM_SELECTITEM avec le flag rendu par TVGN_DROPHILITE (pour retrouver l'handle de l'item qui vient de subir le Déplacer-Poser (Drag and Drop)), mais cette fois-ci, lParam DOIT ÊTRE égal à zéro. Si vous n'avez plus le contrôle de votre Item, vous obtiendrez un étrange effet : quand vous choisissez un autre Item, celui-ci sera contenu dans un rectangle mais le contrôle sera toujours sur le dernier Item. Ensuite, vous devez appeler ImageList_DragLeave suivi d' ImageList_EndDrag. Vous devez relâcher la souris en appelant ReleaseCapture. Si vous avez créé une image list, vous devez la détruire en appelant ImageList_Destroy. Après ça, lorsque l'opération de drag and drop est terminée, vous pouvez continuer le déroulement normal de votre programme.

Code sample:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.const
IDB_TREE equ 4006 ; c'est l'ID de la ressource bitmap (le n° d'Identité de l'image entière)
.data
ClassName db "TreeViewWinClass",0
AppName db "Tree View Demo",0
TreeViewClass db "SysTreeView32",0
Parent db "Parent Item",0
Child1 db "child1",0
Child2 db "child2",0
DragMode dd FALSE ; un flag pour déterminer si nous sommes en mode Drag (traînée)

.data?
hInstance HINSTANCE ?
hwndTreeViewdd ? ; handle du tree view control
hParent dd ? ; handle de l 'Item racine' du Tree View.
hImageList dd ? ; handle de l 'image list' employée dans le 'tree view control'.
hDragImageList dd ? ; handle de l 'image list' où on a l'habitude de stocker l'image de traînée.

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
invoke InitCommonControls

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,200,400,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc uses edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL tvinsert:TV_INSERTSTRUCT
LOCAL hBitmap:DWORD
LOCAL tvhit:TV_HITTESTINFO
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL,\
WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT,0,\
0,200,400,hWnd,NULL,\
hInstance,NULL ; Crée le 'tree view control'
mov hwndTreeView,eax
invoke ImageList_Create,16,16,ILC_COLOR16,2,10 ; Crée l 'image list' associée.
mov hImageList,eax
invoke LoadBitmap,hInstance,IDB_TREE ; Charge le bitmap ressource (charge l'image).
mov hBitmap,eax
invoke ImageList_Add,hImageList,hBitmap,NULL ; Ajoute le bitmap dans l 'image list'.
invoke DeleteObject,hBitmap ; Supprime ce bitmap pour toujours.
invoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageList
mov tvinsert.hParent,NULL
mov tvinsert.hInsertAfter,TVI_ROOT
mov tvinsert.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE
mov tvinsert.item.pszText,offset Parent
mov tvinsert.item.iImage,0
mov tvinsert.item.iSelectedImage,1
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
mov hParent,eax
mov tvinsert.hParent,eax
mov tvinsert.hInsertAfter,TVI_LAST
mov tvinsert.item.pszText,offset Child1
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
mov tvinsert.item.pszText,offset Child2
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
.elseif uMsg==WM_MOUSEMOVE
.if DragMode==TRUE
mov eax,lParam
and eax,0ffffh
mov ecx,lParam
shr ecx,16
mov tvhit.pt.x,eax
mov tvhit.pt.y,ecx
invoke ImageList_DragMove,eax,ecx
invoke ImageList_DragShowNolock,FALSE
invoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit
.if eax!=NULL
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax
.endif
invoke ImageList_DragShowNolock,TRUE
.endif
.elseif uMsg==WM_LBUTTONUP
.if DragMode==TRUE
invoke ImageList_DragLeave,hwndTreeView
invoke ImageList_EndDrag
invoke ImageList_Destroy,hDragImageList
invoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eax
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0
invoke ReleaseCapture
mov DragMode,FALSE
.endif
.elseif uMsg==WM_NOTIFY
mov edi,lParam
assume edi:ptr NM_TREEVIEW
.if [edi].hdr.code==TVN_BEGINDRAG
invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0,[edi].itemNew.hItem
mov hDragImageList,eax
invoke ImageList_BeginDrag,hDragImageList,0,0,0
invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.y
invoke SetCapture,hWnd
mov DragMode,TRUE
.endif
assume edi:nothing
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
end start

Analyse:

Avec WM_CREATE, vous créez tree view control.
    invoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL,\
    WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT,0,\
    0,200,400,hWnd,NULL,\
    hInstance,NULL
Remarquez les styles. TVS_xxxx représentent des styles spéciaux de Tree View.
    invoke ImageList_Create,16,16,ILC_COLOR16,2,10
    mov hImageList,eax
    invoke LoadBitmap,hInstance,IDB_TREE
    mov hBitmap,eax
    invoke ImageList_Add,hImageList,hBitmap,NULL
    invoke DeleteObject,hBitmap
    invoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageList
Ensuite, vous créez une 'image list' vide à son ouverture, laquelle acceptera des images de 16x16 pixels et en couleur 16 bits. Elle contiendra 2 images, mais on peut l'agrandir à 10 images si le besoin s'en fait ressentir. Nous chargeons alors le bitmap et l'ajoutons à l' image list' juste créé. Après ça, nous supprimons l'handle du bitmap puisque il ne sera plus employé désormais. Quand l 'image list' est définitivement finie, nous l'associons au tree view control en envoyant le message TVM_SETIMAGELIST au tree view control.
    mov tvinsert.hParent,NULL
    mov tvinsert.hInsertAfter,TVI_ROOT
    mov tvinsert.u.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE
    mov tvinsert.u.item.pszText,offset Parent
    mov tvinsert.u.item.iImage,0
    mov tvinsert.u.item.iSelectedImage,1
    invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
Nous insérons des Items dans le tree view control, en commençant à partir de l'Item racine. Puisque il s'agit de l'Item racine, le membre hParent est NULL et hInsertAfter est TVI_ROOT. Le membre imask indique que les membres pszText, iImage et iSelectedImage de la structure TV_ITEM sont valables. Nous remplissons ces trois membres des valeurs appropriée. pszText contient le label de l'Item racine (son texte), iImage est l'index de l'image dans l 'image list' qui sera montré à gauche de l'Item non choisi et iSelectedImage est l'index de l'image dans l 'image list' qui sera montré quand l'Item est choisi. Quand tous les membres appropriés sont remplis, nous envoyons le message TVM_INSERTITEM au tree view control pour y ajouter l'Item racine.
    mov hParent,eax
    mov tvinsert.hParent,eax
    mov tvinsert.hInsertAfter,TVI_LAST
    mov tvinsert.u.item.pszText,offset Child1
    invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
    mov tvinsert.u.item.pszText,offset Child2
    invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
Après que l'Item racine ait été rajouté, nous pouvons y attacher les Childs Items. Le membre hParent est maintenant rempli de l'handle de l'Item parent. Et nous emploierons des images identiques dans l 'image list' donc nous ne touchons plus aux membres iImage et iSelectedImage.
    .elseif uMsg==WM_NOTIFY
    mov edi,lParam
    assume edi:ptr NM_TREEVIEW
    .if [edi].hdr.code==TVN_BEGINDRAG
    invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0,[edi].itemNew.hItem
    mov hDragImageList,eax
    invoke ImageList_BeginDrag,hDragImageList,0,0,0
    invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.y
    invoke SetCapture,hWnd
    mov DragMode,TRUE
    .endif
    assume edi:nothing
Maintenant quand l'utilisateur essaye de déplacer un Item, le tree view control envoie le message WM_NOTIFY avec TVN_BEGINDRAG en tant que code. lParam est le pointer qui est sur la structure NM_TREEVIEW laquelle contient plusieurs informations dont nous avons besoin, ainsi nous mettons sa valeur dans edi et employons edi comme pointer pour la structure NM_TREEVIEW. 'assume edi:ptr NM_TREEVIEW' est une façon de dire à MASM de traiter edi en tant que pointer sur la structure NM_TREEVIEW. Nous créons alors une image de traînée en envoyant TVM_CREATEDRAGIMAGE au tree view control. Il renvoie l'handle de l 'image list' nouvellement créé avec une image de traînée à l'intérieur. Nous appelons ImageList_BeginDrag pour définir le point chaud de l'image de traînée. Alors nous passons à l'opération de traînée en appelant ImageList_DragEnter. Cette fonction affiche l'image de traînée à l'emplacement indiqué dans la fenêtre indiquée. Nous employons la structure ptDrag qui est un membre de la structure NM_TREEVIEW en tant que point de départ où l'image de traînée doit être placée au commencement. Après ça, nous capturons l'entrée souris et nous (activons) le flag pour indiquer que nous passons désormais en mode Drag (traînée).
    .elseif uMsg==WM_MOUSEMOVE
    .if DragMode==TRUE
    mov eax,lParam
    and eax,0ffffh
    mov ecx,lParam
    shr ecx,16
    mov tvhit.pt.x,eax
    mov tvhit.pt.y,ecx
    invoke ImageList_DragMove,eax,ecx
    invoke ImageList_DragShowNolock,FALSE
    invoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit
    .if eax!=NULL
    invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax
    .endif
    invoke ImageList_DragShowNolock,TRUE
    .endif
Maintenant nous nous concentrons sur WM_MOUSEMOVE. Quand l'utilisateur traîne l'image, notre fenêtre parente reçoit des messages WM_MOUSEMOVE. En réponse à ces messages, nous mettons à jour la position de l' image de traînée avec ImageList_DragMove. Après ça, nous vérifions si l'image de traînée passe au-dessus d'un Item. Nous faisons ça en envoyant le message TVM_HITTEST au tree view control. ISi l'image de traînée passe par dessus d'un autre Item, nous reprenons le contrôle de notre Item en envoyant le message TVM_SELECTITEM avec le flag TVGN_DROPHILITE (pour dire que c'est l'Item Drag and Drop qui nous intéresse et pas l'autre) au tree view control. Pendant cette opération, nous cachons l'image de traînée pour qu'elle ne laisse pas de taches dégueulasses sur le tree view control.
    .elseif uMsg==WM_LBUTTONUP
    .if DragMode==TRUE
    invoke ImageList_DragLeave,hwndTreeView
    invoke ImageList_EndDrag
    invoke ImageList_Destroy,hDragImageList
    invoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0
    invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eax
    invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0
    invoke ReleaseCapture
    mov DragMode,FALSE
    .endif
Quand l'utilisateur relâche le bouton gauche de la souris, l'opération de traînée se termine. Nous sortons du mode traînée en appelant ImageList_DragLeave, suivi par ImageList_EndDrag et ImageList_Destroy.
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32asm Tutorial 20: Window Subclassing

Tutorial 20: Les Sousclassifications de Fenêtre
(Window Subclassing)

Dans ce Tutorial, nous allons voir les éléments de sousclassifications d'une fenêtre, ce que c'est et comment les employer à votre avantage.
Downloadez l'exemple ici.

Théorie:

Si ça fait un petit moment que vous programmez sous Windows, vous avez dû rencontrer certains cas où une fenêtre possède pratiquement les attributs (les caractéristiques) dont vous auriez besoin pour votre programme, mais pas tout à fait quand même. Vous avez sûrement déjà dû rencontrer cette situation où il vous faut un contrôle d'édition un peu spécial qui puisse filtrer certains types de textes indésirables ! La meilleur chose à faire, c'est de coder votre propre fenêtre. Mais c'est le travail vraiment difficile et très long. Voilà donc à notre secours les 'Sousclassifications' de fenêtre.
Grâce aux sousclassifications de fenêtre on peut "reprendre" (corriger améliorer) une fenêtre sousclassée. Vous y aurez un contrôle absolu. On va prendre un exemple pour que ce soit plus clair. Supposons que vous avez besoin d'une boîte de texte qui n'accepte que des nombres hexadécimaux. Si vous utilisez un simple contrôle d'édition, rien n'interdit à l'utilisateur de taper autre chose que des nombres hexadécimaux. Si l'utilisateur tape "zb+q *" dans la boîte de texte, vous ne pouvez rien en faire sauf rejeter la totalité de cette chaîne de caractères. C'est pas très professionnel. En réalité, vous avez besoin de pouvoir examiner chaque caractère tapé par l'utilisateur au moment même où il le tape dans la boîte de saisie.
Nous allons examiner comment faire çà. Quand l'utilisateur tape quelque chose dans une boîte de texte, Windows envoie le message WM_CHAR à la procédure de fenêtre qui s'occupe de la gestion du contrôle d'édition. Cette procédure de fenêtre réside à l'intérieur de Windows lui-même donc nous ne pouvons rien modifier. Cependant nous pouvons faire suivre le flux de message de notre propre procédure de fenêtre pour que notre procédure de fenêtre devienne la première (avant que Windows s'en occupe) à traiter n'importe quel message envoyé au contrôle d'édition. Si on veut faire réagir notre procédure de fenêtre à un éventuel message (si on veut détecter des caractères non désirés), on doit le faire ainsi. Mais si elle ne veut pas prendre en compte le message, on peut le passer à la procédure de fenêtre originale. De cette manière, notre procédure de fenêtre s'insère entre Windows et le contrôle d'édition. Voyez plutôt le flux ci-dessous :

    Avant la Sousclassification :

    Windows ==> contrôle d'édition de la procédure de fenêtre.

    Après mise en place d'un système de Sousclassification :

    Windows ==> notre procédure de fenêtre -----> contrôle d'édition de la procédure de fenêtre.
Maintenant concentrons notre attention sur le fait de savoir comment sous-classer une fenêtre. Remarquez que la sousclassification n'est pas uniquement limitée aux simples 'controls' (commandes), on peut aussi l'employée avec n'importe quelle fenêtre.
Réfléchissons ! Comment Windows sait à quel endroit est-ce que le contrôle d'édition (de la procédure de fenêtre) réside. Vous avez devinez ? ...... regardez le membre lpfnWndProc de la structure WNDCLASSEX. Si nous pouvons remplacer ce membre par l'adresse de notre propre procédure de fenêtre, alors Windows enverra des messages à notre proc de fenêtre au lieu de l'autre.
On peut faire ça en appelant SetWindowLong.
    SetWindowLong PROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORD
hWnd = handle de la fenêtre où l'on doit changer la valeur à l'intérieur de la structure WNDCLASSEX.
nIndex = valeur à changer.
    GWL_EXSTYLE Défini un nouveau 'style étendu' de fenêtre.
    GWL_STYLE Défini un nouveau style de fenêtre.
    GWL_WNDPROC défini une nouvelle adresse pour la procédure de fenêtre.
    GWL_HINSTANCE Défini une nouvelle application d 'instance handle'.
    GWL_ID Défini un nouvel (ID) identificateur de la fenêtre.
    GWL_USERDATA impose de travailler avec des valeurs de 32 bits pour cette fenêtre. Chaque fenêtre a une valeur correspondante à 32 bits destinée à être utilisée par l'application qui a créé cette fenêtre.
dwNewLong = C'est la valeur de substitution.
Donc notre travail est facile : Nous codons (écrivons) une proc de fenêtre qui manipulera les messages du contrôle d'édition et appellera ensuite SetWindowLong avec flag renvoyé par GWL_WNDPROC, en faisant passer l'adresse de notre proc de fenêtre comme troisième paramètre. Si la fonction réussit, la valeur de retour est la valeur vue précédemment (l'entier 32 bits), dans notre cas, c'est l'adresse de la procédure de fenêtre originale. Nous avons besoin de stocker cette valeur pour pouvoir ensuite l'utiliser dans notre propre procédure de fenêtre.
Rappelez-vous qu'il y a certains messages (ici des chiffres ou des lettres non-hexadécimales) que nous ne souhaitons pas récupérer, donc nous les passerons à la procédure de fenêtre originale. Nous pouvons faire ça en appelant la fonction CallWindowProc.
    CallWindowProc PROTO lpPrevWndFunc:DWORD, \
    hWnd:DWORD,\
    Msg:DWORD,\
    wParam:DWORD,\
    lParam:DWORD
LpPrevWndFunc = adresse de la 'procédure de fenêtre' originale.
Ci-dessus, ces quatre paramètres sont ceux passés à notre procédure de fenêtre. On les passe uniquement grâce à CallWindowProc.

Echantillon de Code :

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "SubclassWinClass",0
AppName db "Subclassing Demo",0
EditClass db "EDIT",0
Message db "You pressed Enter in the text box!",0

.data?
hInstance HINSTANCE ?
hwndEdit dd ?
OldWndProc dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR EditClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
20,300,25,hWnd,NULL,\
hInstance,NULL
mov hwndEdit,eax
invoke SetFocus,eax
;-----------------------------------------
; On le sous-classe !
;-----------------------------------------
invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc
mov OldWndProc,eax
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp

EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_CHAR
mov eax,wParam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
ret
.endif
.elseif uMsg==WM_KEYDOWN
mov eax,wParam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
EditWndProc endp
end start

Analyse:

    invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc
    mov OldWndProc,eax
Après que le contrôle d'édition soit créé, nous le sous-classons en appelant SetWindowLong, en remplaçant l'adresse de la procédure de fenêtre originale par notre propre procédure de fenêtre. Remarquez que nous stockons l'adresse de la procédure de fenêtre originale pour pouvoir l'utiliser plus tard avec CallWindowProc. Notez qu' EditWndProc est une procédure de fenêtre ordinaire.
    .if uMsg==WM_CHAR
    mov eax,wParam
    .if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
    .if al>="a" && al<="f"
    sub al,20h
    .endif
    invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
    ret
    .endif
Grâce à EditWndProc, nous filtrons les messages WM_CHAR. Si le caractère est compris entre 0-9 ou A-F, nous l'acceptons en le laissant arriver jusqu'à la procédure de fenêtre originale. Si c'est une lettre comprise entre A-F, nous la convertissons en lettre entre a-f en ajoutant 20h à la valeur hexadécimale qui représente cette lettre. Notez bien que, si le caractère n'est pas celui nous attendons, nous le rejetons. Nous ne le passons pas à la procédure de fenêtre originale. Donc lorsque l'utilisateur tape quelque chose d'autre que 0-9 ou a-f, le caractère n'apparaît pas dans le contrôle d'édition.
    .elseif uMsg==WM_KEYDOWN
    mov eax,wParam
    .if al==VK_RETURN
    invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
    invoke SetFocus,hEdit
    .else
    invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
    ret
    .end
Je vous démontre en plus (dans le programme) la puissance de la méthode de sous-classification avec la détection de la touche Entre. EditWndProc vérifie si la valeur du message WM_KEYDOWN est VK_RETURN (la Touche Enter). Si c'est le cas, une boîte de message affiche s'ouvre pour afficher : "You pressed the Enter key in the text box!" (Vous avez appuyé sur la touche Enter dans la boîte de texte!). Si ce n'est pas la touche Enter, on passe le message à la procédure de fenêtre originale.
Vous pouvez employer la méthode de sous-classification de fenêtre pour prendre le contrôle d'autres fenêtres. C'est une des corde que vous devez avoir à votre arc.
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 21: Pipe

Tutorial 21: Le Pipe
Pipe = Cable de communication entre deux programmes

(en plus : couleur du fond d'un contrôle d'édition)

Dans ce Tutorial, nous allons nous intéresser au 'Pipe', ce que ça représente et à quoi nous pouvons l'employer. Pour rendre tout ça plus intéressant, j'ajoute en plus à cette technique le fait de savoir comment changer le fond et la couleur du texte d'un contrôle d'édition.
Downloadez l'exemple ici.
En réalité cet exemple fait appel au programme ml.exe qui se trouve dans C:\Masm32\Bin\ donc vous devez d'abord le dézipper dans ce dossier pour qu'il puisse fonctionner.

Théorie:

Le pipe est un conduit ou bien une sorte de cable de communication muni de deux bouts. Vous pouvez l'employer pour échanger des données entre deux process différents (entre deux programmes différents), ou dans un même process. C'est comme un walkie-talkie. Vous donnez une information à l'autre partie et elle peut l'employer pour communiquer avec vous.
Il y a deux types de pipes : les pipes anonymes et les pipes nommées. Un pipe anonyme est, eh bien, anonyme : c'est-à-dire que vous pouvez l'utiliser sans connaître son nom. Pour un pipe nommée c'est le contraire : vous devez connaître son nom avant de pouvoir l'utiliser.
Vous pouvez aussi ranger les pipes par catégories selon leur propriété : certains fonctionnent en sens unique, les autres communique bilatéralement. Dans un pipe à sens unique, les données circulent uniquement dans une seule direction : d'un bout à l'autre. Tandis que pour un pipe bilatérale, les données peuvent être échangées entre les deux bouts.
Un pipe anonyme est toujours à sens unique tandis qu'un pipe nommée peut être à sens unique ou bilatérale. Un pipe nommée est d'habitude employé dans un environnement réseau dans lequel un serveur peut se connecter à plusieurs clients.
Dans ce Tutorial, nous examinerons le pipe anonyme en détail. Le but principal d'un pipe anonyme c'est d'être utilisé comme un cable de communcation entre un parent et des process enfants (entre un programme principal et ses sous-programmes) ou entre plusieurs process enfants.
Un pipe anonyme est réellement utile lorsque vous traitez avec une application console. Une application console est une sorte de programme win32 qui utilise une console en tant qu'Entrée/Sortie. Une console est un outils de DOS. Cependant, une application de console est un programme entièrement 32 bits. Elle peut employer n'importe quelle fonction du GUI (NdT : Graphic User Interface, le GUI c'est l'ensemble des objets tels qu'une boîte de saisie de texte, les boutons…), de la même façon que d'autres programmes.
Une application console a trois handles qu'elle peut utiliser pour ses Entrées/Sorties. Ont les appelle 'handles standards'. Il y en a trois : 'Entrée Standard'(ou Standard Input), 'Sortie Standard'(ou Standard Output) et 'Erreur Standard'(ou Standard Error). L'handle de l'Entrée Standard est employé pour lire/récupérer les informations de la console, alors que l'handle de la Sortie Standard est utilisé pour renvoyer les données vers la sortie/ou l 'imprimante. L'handle de l'Erreur Standard est employé pour donner un compte rendu sur les conditions d'erreurs lorsque sa sortie n'est pas accessible.
Une application console peut retrouver ces trois 'handles standards' en appelant la fonction GetStdHandle, en spécifiant bien quel handle est ce qu'on souhaite récupérer. Une application GUI n'a pas de console. Si vous appelez GetStdHandle, il renverra une erreur. Si vous voulez vraiment utiliser une console, vous pouvez appeler AllocConsole pour allouer une nouvelle console. Cependant, n'oubliez pas d'appeler FreeConsole quand vous mettez en place une console.
Le pipe anonyme est fréquemment employée pour faire suivre les entrées/sorties d'une console d'une application enfant. Le process parent peut être une console ou une application du GUI, mais l'enfant doit être un appendice de console. Pour que ça marche. Comme vous le savez, une application console utilise des handles standards pour ses Entrées/Sorties. Si nous voulons faire suivre les Entrées/Sorties d'une application console, nous pouvons remplacer l'handle par un des handles du pipe. Une application console ne se rendra pas compte qu'on emploie l'handle d'un pipe. Elle l'utilisera de la même manière qu'un handle normal. C'est une sorte de polymorphisme, en le jargon OOP. Cette approche est puissante puisque nous n'avons pas besoin de modifier le process enfant où que ce soit.
Une autre chose que vous devez savoir à propos des applications console c'est à quel moment on récupère ces 'handles standards'. Lorsqu'une application console est créée, le parent process a deux choix : il peut créer une nouvelle console pour l'enfant ou il peut laisser l'enfant hériter de sa propre console (le parent process et le child prosses auront des consoles de mêmes caractéristiques). Avec la deuxième approche, le parent process doit être une application console ou bien si c'est une application du GUI, elle doit d'abord appeler AllocConsole pour allouer une console.
On va commencer le boulot. Pour créer un pipe anonyme vous avez besoin d'appeler CreatePipe. CreatePipe a le prototype suivant :
CreatePipe proto pReadHandle:DWORD, \
pWriteHandle:DWORD,\
pPipeAttributes:DWORD,\
nBufferSize:DWORD
  • pReadHandle est un pointer qui est sur une variable de type dword qui recevra l'handle d'écriture du pipe.
  • pWriteHandle est un pointer qui est sur une variable de type dword laquelle recevra l 'handle d'écriture' du pipe.
  • pPipeAttributes pointe sur la structure SECURITY_ATTRIBUTES laquelle détermine si les handles de lecture et d'écriture renvoyés ont bien étés générés par un process enfant.
  • nBufferSize est la taille supposée du buffer que le pipe réservera pour l'utilisation. C'est seulement une taille supposée. Vous pouvez employer NULL pour dire à la fonction d'employer la taille par défaut.
Si l'appel est couronné de succès, la valeur en retour est différent de zéro. Si elle a échouée, la valeur de retour est nulle.
Après que l'appel est été couronné de succès, vous obtiendrez deux handles, un pour le bout 'lecture' du pipe, et autre pour le bout 'écriture'. Maintenant on va voir les étapes nécessaires à la réexpédition des Sorties Standards d'un programme console enfant, vers votre propre process. Notez que ma méthode diffère de celle de la référence win32 api de Borland. La méthode dans la référence win32 api impose que le parent process soit une application console et par conséquent le prosses enfant peut hériter de ses 'handles standards'. Mais la plupart du temps, nous aurons besoin de faire suivre la Sortie d'une application console vers une application du GUI (par exemple vers une boîte de saisie).
  1. Créez une pipe anonyme avec CreatePipe. N'oubliez pas de mettre le membre bInheritable à TRUE dans la structure SECURITY_ATTRIBUTES puisque les handles ne proviennent pas d'un parent.
  2. Maintenant nous devons préparer les paramètres que nous passerons à CreateProcess puisque nous l'emploierons pour charger l'application console enfant. Une structure importante est la structure de STARTUPINFO. Elle détermine l'apparence de la fenêtre principale du process enfant quand elle apparaît en premier lieu. Cette structure est essentielle pour nous. Vous pouvez cacher la fenêtre principale et passer l'handle du pipe au process console enfant comme ça. Ci-dessous voici les membres que vous devez remplir :
    • cb : est la taille de structure de STARTUPINFO
    • dwFlags : les flags binaires (donc 0 ou 1) qui déterminent si les membres de la structure sont valables, ils déterminent aussi l'état apparent/caché de la fenêtre principale. Pour notre but, on doit utiliser une combinaison des deux flags : STARTF_USESHOWWINDOW et STARTF_USESTDHANDLES
    • hStdOutput et hStdError : sont les handles qui seront utilisés en tant 'handle standard de sortie' et 'handle standard d'erreur' pour votre process enfant. Pour arriver à notre but, nous passons l'handle d'écriture du pipe en tant que sortie/erreur standard du process enfant. De cette façon, lorsque les Sorties enfants sont en actions (Sorties/Erreurs standard), on passe en réalité les renseignements par le pipe vers le parent process.
    • wShowWindow a un contrôle sur l'état affiché/caché de la fenêtre principale. Dans notre cas, nous ne souhaitons pas que la console de notre fenêtre enfant soit montrée donc nous mettons SW_HIDE dans ce membre.
  3. On appelle CreateProcess pour charger l'application enfant. Après que CreateProcess soit couronné de succès, l'enfant est toujours inactivé. Il est chargé en mémoire mais il n'est pas immédiatement en action.
  4. On ferme l'handle d'écriture du pipe. C'est nécessaire. Parce que le parent process n'a pas besoin de se servir de l'handle d'écriture du pipe. En plus, le pipe ne fonctionnera pas si il y a plus d'une sortie d'écriture, nous DEVONS le fermer avant la lecture des données du pipe. Cependant, ne fermez pas l'handle d'écriture avant l'appel CreateProcess, sinon le fonctionnement de votre pipe sera transgressé. Vous devez seulement le fermer après le retour de CreateProcess mais surtout pas avant que vous n'ayez lu les données de lecture du pipe.
  5. Maintenant vous pouvez lire les données du bout de lecture du pipe avec ReadFile. Grâce à ReadFile, vous influencez le process enfant pendant son déroulement même. Ça débute l'exécution et lorsqu'on ''écrit'' quelque chose à l'handle standard de sortie (qui est en réalité l'handle d'écriture du pipe), les données sont envoyées par le pipe vers le bout d'écriture. Vous pouvez vous représenter ReadFile un peu comme un aspirateur de données du bout d'écriture du pipe. Vous devez appeler ReadFile à plusieurs reprises avant qu'il ne retourne la valeur 0, ce qui signifie qu'il n'y a plus de données à lire. Vous pouvez faire ce qu'il vous plais des données lues dans le pipe. Dans notre exemple, je les ai mises dans un contrôle d'édition. (cas classique)
  6. On ferme l'handle de lecture du pipe.

Example:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.const
IDR_MAINMENU equ 101 ; l' ID du menu principal
IDM_ASSEMBLE equ 40001

.data
ClassName db "PipeWinClass",0
AppName db "One-way Pipe Example",0 EditClass db "EDIT",0
CreatePipeError db "Error during pipe creation",0
CreateProcessError db "Error during process creation",0
CommandLine db "ml /c /coff /Cp test.asm",0

.data?
hInstance HINSTANCE ?
hwndEdit dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,IDR_MAINMENU
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,400,200,NULL,NULL,\ hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL rect:RECT
LOCAL hRead:DWORD
LOCAL hWrite:DWORD
LOCAL startupinfo:STARTUPINFO
LOCAL pinfo:PROCESS_INFORMATION
LOCAL buffer[1024]:byte
LOCAL bytesRead:DWORD
LOCAL hdc:DWORD
LOCAL sat:SECURITY_ATTRIBUTES
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,addr EditClass, NULL, WS_CHILD+ WS_VISIBLE+ ES_MULTILINE+ ES_AUTOHSCROLL+ ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, NULL, hInstance, NULL
mov hwndEdit,eax
.elseif uMsg==WM_CTLCOLOREDIT
invoke SetTextColor,wParam,Yellow
invoke SetBkColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret
.elseif uMsg==WM_SIZE
mov edx,lParam
mov ecx,edx
shr ecx,16
and edx,0ffffh
invoke MoveWindow,hwndEdit,0,0,edx,ecx,TRUE
.elseif uMsg==WM_COMMAND
.if lParam==0
mov eax,wParam
.if ax==IDM_ASSEMBLE
mov sat.niLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE
invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL
.if eax==NULL
invoke MessageBox, hWnd, addr CreatePipeError, addr AppName, MB_ICONERROR+ MB_OK
.else
mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE
invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo
.if eax==NULL
invoke MessageBox,hWnd,addr CreateProcessError,addr AppName,MB_ICONERROR+MB_OK
.else
invoke CloseHandle,hWrite
.while TRUE
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw
.endif
invoke CloseHandle,hRead
.endif
.endif
.endif
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret
.endif
xor eax,eax
ret
WndProc endp
end start

Analyse:

Notre exemple appellera le petit programme ml.exe pour assembler un fichier nommé test.asm, puis fera suivre la sortie (les données en sortie) de ml.exe vers le contrôle d'édition (du secteur client de notre programme).
Quand le programme est chargé, on enregistre la 'window class' et on crée comme d'habitude la fenêtre principale. La première chose qu'on fait pendant la création de la fenêtre principale c'est de créer un contrôle d'édition qui sera employé pour afficher les données de sortie de ml.exe (pour afficher par exemple: ml.exe vient d'assembler Test.asm avec succès…..).
Maintenant nous voici dans la partie intéressante (mais c'est le deuxième point de ce tutorial. Ça n'a aucun rapport avec ce qu'on a vu avant), nous allons changer la couleur du texte et la couleur de fond du contrôle d'édition. Quand un contrôle d'édition repeindre son secteur client, il envoie le message WM_CTLCOLOREDIT à sa fenêtre parente..
wParam contient l'handle du style d'écriture que le contrôle d'édition emploiera pour écrire son propre secteur de client. Nous pouvons profiter de cette occasion pour modifier les caractéristiques de l'HDC.
.elseif uMsg==WM_CTLCOLOREDIT
invoke SetTextColor,wParam,Yellow
invoke SetTextColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret
SetTextColor change la couleur du texte en jaune. SetTextColor change la couleur du fond en noir. Et finalement, nous obtenons l'handle du pinceau noir que nous renvoyons à Windows. Avec le message WM_CTLCOLOREDIT, on renvoie l'handle du pinceau que Windows emploiera pour repeindre le fond du contrôle d'édition. Dans notre exemple, je souhaite que le fond soit noir, donc je renvoie à Windows l'handle du pinceau noir.
Maintenant quand l'utilisateur choisit le menuitem (le sous-menu) Assemble, il crée un pipe anonyme.

.if ax==IDM_ASSEMBLE
mov sat.niLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE

Avant l'appel CreatePipe, nous devons d'abord remplir la structure SECURITY_ATTRIBUTES. Notez que nous pouvons employer NULL dans le membre lpSecurityDescriptor si nous ne nous soucions pas de la sécurité. Et le membre bInheritHandle doit être TRUE pour que les handles du pipe soient les mêmes pour le process parent et le process enfant.

invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL

Après ça, nous appelons CreatePipe qui, si il est couronné de succès, remplira les variables hRead et hWrite respectivement avec les handles de lecture et d'écriture des bouts du pipe.

mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE

Ensuite nous devons remplir la structure STARTUPINFO. Nous appelons GetStartupInfo pour remplir la structure STARTUPINFO avec les valeurs de défaut du process parent. Vous DEVEZ remplir la structure STARTUPINFO avec cet appel si vous avez l'intention que votre code puisse travailler à la fois sous win9x et NT. Après les valeurs retournées par l'appel GetStartupInfo, vous pouvez modifier les membres qui sont importants. Nous copions l'handle d'écriture du bout du pipe dans hStdOutput et hStdError puisque nous voulons que le process enfant l'utilise au lieu des handles standards de Sortie/Erreur par défaut. Nous souhaitons aussi cacher la fenêtre de console du process enfant, donc nous mettons la valeur SW_HIDE dans le membre wShowWidow. Et finalement, nous devons indiquer que les membres hStdOutput, hStdError et wShowWindow sont valables (ils sont utilisables car ils sont porteur d'une information) et doivent être utilisés en plaçant les flags STARTF_USESHOWWINDOW et STARTF_USESTDHANDLES dans le membre dwFlags.

invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo

Nous créons maintenant le process enfant grâce à l'appel CreateProcess. Remarquez que le paramètre bInheritHandles doit être mis à TRUE pour que l'handle du pipe soit le même que son parent, et ainsi fonctionne normalement.

invoke CloseHandle,hWrite

Après que nous ayons créé le process enfant avec succès, nous devons fermer le bout d'écriture du pipe. Rappelez-vous que nous avons passé l'handle d'écriture au process enfant via la structure STARTUPINFO. Si nous ne fermons pas cet handle nous même, il y aura deux bouts d'écriture. Et alors le pipe ne fonctionnera pas. Nous devons fermer l'handle d'écriture : Après CreateProcess, mais aussi, Avant que nous ne lisions les données appartenant au bout de lecture du pipe.

.while TRUE
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw

Maintenant nous sommes prêts à lire les données de la Sortie Standard du process enfant. Nous tournerons dans une boucle infinie jusqu'à ce qu'il n'y ait plus de données de lecture. Nous appelons RtlZeroMemory pour qu'il remplisse le buffer avec des zéros lors de l'appel ReadFile, en passant l'handle de lecture du pipe au lieu de l'handle d'un fichier (pour lire dans le Buffer et non dans un fichier). Notez bien que nous ne pouvons lire qu'un maximum de 1023 octets puisque nous avons besoin des données qu'en tant que chaîne de caractères ASCIIZ, lesquels nous pouvons maintenant passer au contrôle d'édition.
Lorsque ReadFile nous retourne les données du buffer, nous les envoyons dans le contrôle d'édition. Cependant, il y a un léger problème ici. Si nous employons SetWindowText pour mettre les données dans le contrôle d'édition, nos nouvelles données recouvriront et effaceront des données déjà existantes! Nous préférons plutôt que nos données s'ajoutent à la fin des données existantes.
Pour réussir à faire ça, nous devons d'abord déplacer notre curseur à la fin du texte dans le contrôle d'édition en envoyant le message EM_SETSEL avec wParam =-1. Ensuite, nous ajoutons les données à partir de ce point grâce au message EM_REPLACESEL.

invoke CloseHandle,hRead

Dès que ReadFile renvoie le NULL, nous sortons de la boucle de lecture et refermons justement l'handle de lecture.

Iczelion's Win32 Assembly Homepage

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 22: SuperClassing

Tutorial 22: Superclassification
(Superclassing)

Dans ce Tutorial, nous allons voir, ce qu'est la superclassification et à quoi ça sert. Vous allez aussi apprendre à munir vos 'Controls' de la fonction de la touche TAB. (vous savez ! en appuyant sur TAB on met en sélection les boutons ou le contrôle d'édition… les uns après les autres)
Downloadez l'exemple ici

Théorie:

Dans votre carrière de programmeur, vous avez sûrement déjà rencontré une situation où vous avez besoin de plusieurs 'Controls' avec un comportement *peu* différent. Par exemple, vous pouvez avoir besoin de 10 'contrôles d'édition' qui acceptent seulement des nombres. Il y a plusieurs façons de réaliser ceci :
  • Créez votre propre 'window class' et instance.
  • Créez ce 'contrôle d'édition' et ensuite sous-classez chacun d'entre eux (les prochains).
  • Superclassez le contrôle d'édition
La première méthode est trop ennuyeuse. On doit réaliser chaque fonctionnalité du contrôle d'édition par nous-même. Il vaut mieux laisser tomber ça. La deuxième méthode est meilleure que la première, mais c'est encore un travail de longue haleine. C'est bon si vous sous-classez uniquement quelques 'controls' mais ce sera un véritable cauchemar si vous sous-classer plus d'une douzaine de 'Controls'. La superclassification est la technique que vous devez employer pour cette occasion.
La sousclassification est la méthode qu'on doit utiliser pour *prendre le contrôle* d'une classe de fenêtre particulière. Par *la prise de contrôle*, je veux dire que vous pouvez modifier les propriétés de la (Window Class) classe de fenêtre pour la modeler à votre convenance, ainsi on crée une série de 'Controls'.
Les étapes de la superclassification sont décrites ci-dessous :
  • call GetClassInfoEx sert à obtenir l'information sur la 'Window Class' vous voulez pour la superclasse. GetClassInfoEx exige que le pointer soit sur la structure WNDCLASSEX laquelle sera remplie de l'information si l'appel est renvoyé avec succès.
  • Modifiez les membres que vous voulez dans la structure WNDCLASSEX. Cependant, il y a deux membres que vous DEVEZ changer :
    • hInstance Vous devez mettre l'instance handle de votre programme dans ce membre.
    • lpszClassName Vous devez mettre ici le pointer qui est sur le nouveau 'class name'.

      • Vous n'avez pas besoin de modifier le membre lpfnWndProc, mais la plupart du temps, vous avez quand même besoin de le faire. Surtout, n'oubliez pas de sauver le membre lpfnWndProc d'origine si vous souhaitez l'appeler avec CallWindowProc.
  • Enregistrez votre structure WNDCLASSEX modifiée. Vous aurez une nouvelle (Window Class) classe de fenêtre qui aura plusieurs des caractéristiques de la vieille classe de fenêtre.
  • Créez des fenêtres avec la nouvelle 'class'.
La superclassification, c'est mieux que la sousclassification si vous souhaitez créer pas mal de 'Controls' ayant les mêmes caractéristiques.

Exemple:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WM_SUPERCLASS equ WM_USER+5
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "SuperclassWinClass",0
AppName db "Superclassing Demo",0
EditClass db "EDIT",0
OurClass db "SUPEREDITCLASS",0
Message db "You pressed the Enter key in the text box!",0

.data?
hInstance dd ?
hwndEdit dd 6 dup(?)
OldWndProc dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE+WS_EX_CONTROLPARENT,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,220,NULL,NULL,\
hInst,NULL
mov hwnd,eax

.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc uses ebx edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL wc:WNDCLASSEX
.if uMsg==WM_CREATE
mov wc.cbSize,sizeof WNDCLASSEX
invoke GetClassInfoEx,NULL,addr EditClass,addr wc
push wc.lpfnWndProc
pop OldWndProc
mov wc.lpfnWndProc, OFFSET EditWndProc
push hInstance
pop wc.hInstance
mov wc.lpszClassName,OFFSET OurClass
invoke RegisterClassEx, addr wc
xor ebx,ebx
mov edi,20
.while ebx<6
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
edi,300,25,hWnd,ebx,\
hInstance,NULL
mov dword ptr [hwndEdit+4*ebx],eax
add edi,25
inc ebx
.endw
invoke SetFocus,hwndEdit
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp

EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_CHAR
mov eax,wParam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
ret
.endif
.elseif uMsg==WM_KEYDOWN
mov eax,wParam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.elseif al==VK_TAB
invoke GetKeyState,VK_SHIFT
test eax,80000000
.if ZERO?
invoke GetWindow,hEdit,GW_HWNDNEXT
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDFIRST
.endif
.else
invoke GetWindow,hEdit,GW_HWNDPREV
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDLAST
.endif
.endif
invoke SetFocus,eax
xor eax,eax
ret
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
EditWndProc endp
end start

Analyse:

Le programme créera une simple fenêtre avec 6 contrôles d'édition "retouchés" dans son secteur client. Les contrôles d'édition accepteront uniquement des chiffres hexdécimaux. En réalité, j'ai modifié l'exemple de 'Sousclassification' pour le transformer en superclassification. Le début du programme reste le même mais la partie intéressante c'est quand on crée la fenêtre principale :

.if uMsg==WM_CREATE
mov wc.cbSize,sizeof WNDCLASSEX
invoke GetClassInfoEx,NULL,addr EditClass,addr wc

Nous devons d'abord remplir la structure de WNDCLASSEX avec les données de class que nous souhaitons imposer comme superclasse, dans ce cas, c'est (l'Edit Class) la classe D'ÉDITION. Rappelez-vous aussi que vous devez mettre le membre cbSize (de la structure WNDCLASSEX) avant que vous n'appeliez GetClassInfoEx, sinon la structure WNDCLASSEX ne sera pas correctement remplie. Après les retours de GetClassInfoEx, wc est rempli de toute l'information que nous avons besoin pour créer une nouvelle 'Window Class'.

push wc.lpfnWndProc
pop OldWndProc
mov wc.lpfnWndProc, OFFSET EditWndProc
push hInstance
pop wc.hInstance
mov wc.lpszClassName,OFFSET OurClass

Maintenant nous devons modifier certains membres de wc. Le premier c'est le pointer de la procédure de fenêtre. Puisque nous avons besoin d'enchaîner notre propre procédure de fenêtre avec l'originale, nous devons la sauvegarder dans une variable, c'est pourquoi on l'appelle avec CallWindowProc. Cette technique est identique à la sousclassification sauf que vous modifiez directement la structure WNDCLASSEX sans avoir besoin d'appeler SetWindowLong. Les deux membres suivants doivent aussi être modifiés, sinon vous serez dans l'incapacité d'enregistrer votre nouvelle 'Window Classe',idem pour ce qui concerne hInstance et lpsClassName. Vous devez remplacer l'hInstance original par l'hInstance de votre propre programme. Et vous devez choisir un nouveau nom pour la nouvelle 'class'.

invoke RegisterClassEx, addr wc

Quand tout est enfin prêt, on registre la nouvelle classe. on obtient une nouvelle class avec quand même certaines caractéristiques de la vieille class.

xor ebx,ebx
mov edi,20
.while ebx<6
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
edi,300,25,hWnd,ebx,\
hInstance,NULL
mov dword ptr [hwndEdit+4*ebx],eax
add edi,25
inc ebx
.endw
invoke SetFocus,hwndEdit

Maintenant que nous avons enregistré la class, nous pouvons y créer des fenêtres de base. Dans le susdit petit bout de code, j'emploie ebx en tant que compteur de nombre de fenêtres créées. edi est employé comme coordonnée 'Y' du coin gauche supérieur de la fenêtre. Quand une fenêtre est créée, son handle est stocké dans un tableau (informations de type dword). Quand toutes les fenêtres sont créées, le centre d'entrée est sur la première fenêtre. (le centre d'entrée = ex : un bouton est encadré de pointillé où un contrôle d'édition est activée [curseur présent], alors c'est que le centre d'entrée est sur lui.)
À ce niveau, on a 6 contrôles d'édition qui acceptent uniquement des chiffres hexadécimaux. La procédure de fenêtre de substitution manipule les filtres (donc pas de caractère autres que 0-9 ou a-f). En réalité, c'est la même chose que pour la procédure de fenêtre de l'exemple de sousclassification. Donc, comme vous pouvez le voir, vous n'avez pas besoin de refaire le travail de sousclassification pour chacun des contrôles d'édition.

Pour rendre cet exemple encore plus intéressant, je rajoute un petit bout de code qui manipule le contrôle de navigation grâce à la touche TAB. Normalement, si vous mettez des 'Controls' dans une boîte de dialogue, le manager de boîte de dialogue gère les touches de navigation pour vous. De cette manière vous pouvez grâce à la touche TAB passer au contrôle suivant ou bien avec Shift-Tab revenir au contrôle précédent. Hélas, cette particularité n'est pas disponible si vous placez vos 'Controls' dans une simple fenêtre (au lieu d'une Dialog Box). Vous devez les sous-classer, comme ça vous pourrez gérer les touches Tab et Shift-Tab par vous-même. Dans notre exemple, nous n'avons pas besoin de sous-classer les 'Controls' l'un après l'autre vu qu'on les a déjà superclassés, donc nous pouvons leur fournir "un manager de navigation de contrôle" commun.

.elseif al==VK_TAB
invoke GetKeyState,VK_SHIFT
test eax,80000000
.if ZERO?
invoke GetWindow,hEdit,GW_HWNDNEXT
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDFIRST
.endif
.else
invoke GetWindow,hEdit,GW_HWNDPREV
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDLAST
.endif
.endif
invoke SetFocus,eax
xor eax,eax
ret

Le susdit petit bout de code est la procédure EditWndClass. Il vérifie si l'utilisateur appuie sur la touche TAB, si c'est le cas, on appelle GetKeyState pour vérifier si la TOUCHE DES MAJUSCULES (SHIFT) est aussi pressée. GetKeyState renvoie une valeur dans eax qui détermine si la touche indiquée est pressée ou non. Si la touche est appuyée, le Bit de poids fort de eax est rempli. Si 'SHIFT' n'est pas pressé, on laisse ce Bit à 0. Donc on compare la valeur de retour avec 80000000. Si le Bit de poids fort est à 8, ça signifie que l'utilisateur a appuyé sur shift+tab, que nous devons gérer séparément.
Un Dword , donc 32 bit donc (0000 0000 0000 0000 0000 0000 0000 0000)h on met sa partie haute à 1 (1111 0000 0000 0000 0000 0000 0000 0000)h =(8 0 0 0 0 0 0 0)d, car Masm manipule des décimaux par défaut.
Si l'utilisateur appuie seulement sur la touche TAB, nous appelons GetWindow pour retrouver l'handle du contrôle suivant. Nous utilisons le flag GW_HWNDNEXT pour dire à GetWindow de récupérer l'handle de la fenêtre qui suit la ligne du hEdit courant. Si cette fonction renvoie un NULL, on ne le considère pas comme un handle, ainsi le courant hEdit est le dernier contrôle. Nous "revenons" donc au premier contrôle en appelant GetWindow avec son flag GW_HWNDFIRST. C'est la même chose pour la configuration SHIFT-TAB que pour la touche TAB, sauf qu'on inverse tout.

[Iczelion's Win32 Assembly Homepage]
Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 23: Tray Icon

Tutorial 23: Icône de Barre de Taches
(Tray Icon)

Dans ce Tutorial, nous allons voir comment mettre des icônes dans le 'System Tray'(système de barre de tâches) et comment créer/employer un menu popup.
Downloadez l'exemple ici. Vous obtiendrez une fenêtre, qui si vous la réduisez au maximum placera son icône dans la barre de tâches au lieu de la laisser à côté du menu 'Démarrer'.

Théorie:

Le 'System Tray' est la région rectangulaire dans la barre de tâche où plusieurs icônes sont rangées. Normalement, vous y verrez au moins l'icône de l'horloge digitale. Vous pouvez aussi mettre des icônes sur le 'System Tray'. Ci-dessous voici les étapes que vous devez exécuter pour mettre une icône sur le 'System Tray' :
  1. Remplissez la structure NOTIFYICONDATA avec les membres suivants:
    • cbSize est la taille de cette structure.
    • hwnd est l'handle de la fenêtre qui recevra l'avis (l'information) quand un événement souris arrive au 'Tray Icon'. (le Tray Icon c'est donc l'Icône de notre programme lorsquelle est réduite dans la barre de tâches)
    • uID est une constante qui est employée comme identificateur de cette icône. C'est à vous de choisir cette valeur. Au cas où vous avez plus d'une 'Tray Icon', vous serez ainsi capables de vérifier sur quelle 'Tray Icon' la souris agit.
    • uFlags indique que les membres de cette structure sont valables.
      • NIF_ICON le membre 'hIcon' est valide.
      • NIF_MESSAGE le membre uCallbackMessage est valide.
      • NIF_TIP le membre szTip est valide.
    • uCallbackMessage est notre message créé sur mesures (custum message) que Windows enverra à la fenêtre spécifiée grâce au membre hwnd, lorsqu'on clique sur notre 'tray icon' avec la souris. Il faudra créez ce message par nous-même.
    • hIcon est l'handle de l'icône que l'on souhaite mettre dans le 'system tray'.
    • szTip est un tableau de 64 octets qui contient la chaîne de caractères qui sera employée en tant que texte 'tooltip' quand la souris plane au dessus du 'tray icon'. (un texte tooltip, vous savez! c'est une sorte de bulle qui sert à informer sur la nature d'un objet).
  2. Call Shell_NotifyIcon laquelle est définie dans shell32.inc. Cette fonction a le prototype suivant :

    3. Shell_NotifyIcon PROTO dwMessage:DWORD ,pnid:DWORD

    dwMessage est le type de message à envoyer au Shell.
    NIM_ADD ajoute une icône au secteur de statut.
    NIM_DELETE supprime une icône du secteur de statut.
    NIM_MODIFY modifie une icône dans le secteur de statut.
    pnid est le pointer qui est sur la structure NOTIFYICONDATA. Elle doit être remplie des valeurs appropriées.
    Si vous voulez ajouter une icône dans le 'System Tray', vous devez utiliser le message NIM_ADD, si vous voulez enlever l'icône, il faut employer NIM_DELETE.

C'est tous pour le moment. Mais la plupart du temps, on ne se contente pas seulement de mettre une icône à cet endroit. On a besoin d'être capables de répondre aux événements (au clique) de la souris sur un 'tray icon'. On peut faire ça en traitant le message que nous avons spécifié dans le membre uCallbackMessage de la structure NOTIFYICONDATA. Ce message prend les valeurs suivantes dans wParam et lParam (un petit remerciement spécial à s__d pour ses renseignements) :
  • wParam contient l'ID de l'icône. C'est la même valeur que celle que vous mettez dans le membre uID de structure NOTIFYICONDATA.
  • lParam est le mot de poids fable qui contient le message de la souris. Par exemple, si l'utilisateur effectue un clique de droit sur l'icône, lParam contiendra WM_RBUTTONDOWN.
Cependant, beaucoup de 'Tray Icons' déploient un menu popup quand l'utilisateur clique dessus. Nous pouvons exécuter cette particularité en créant un menu popup et en appelant ensuite TrackPopupMenu pour l'afficher. Les étapes sont décrits ci-dessous :
  1. Créez un menu popup en appelant CreatePopupMenu. Cette fonction crée un menu vide. Elle renvoie l'handle du menu dans eax, en cas de succès.
  2. Ajoutez-y des Items de menu grâce à AppendMenu, InsertMenu or InsertMenuItem.
  3. Lorsque vous souhaitez montrer le menu popup à l'endroit où est placée le curseur de la souris, appelez GetCursorPos pour obtenir la coordonnée du curseur sur l'écran puis appeler ensuite TrackPopupMenu pour afficher le menu. Quand l'utilisateur choisit un Item (un article) du menu popup (c'est aussi un sous menu si vous préférez), Windows envoie le message WM_COMMAND à votre procédure de fenêtre exactement comme pour une sélection d'un menu normal.
Note : Prenez garde de deux choses un peu ennuyeuses lorsque vous utilisez un menu popup avec un 'Tray Icon' :
  1. Si vous cliquez n'importe où à l'extérieur du menu popup lorsqu'il est affiché, celui-ci ne disparaîtra pas immédiatement comme il devrait le faire. Ça se passe comme ça parce que la fenêtre qui recevra les avis du menu popup DOIT ÊTRE la fenêtre de premier plan. Le simple appel SetForegroundWindow corrigera ce défaut.
  2. Après l'appel SetForegroundWindow, vous constaterez que la toute première fois où le menu popup est affiché, ça fonctionne bien mais les fois suivantes, notre menu popup s'ouvrira puis se fermera immédiatement. Ce comportement n'est "pas intentionnel", pour citer MSDN. Le commutateur de tâche du programme, lequel contrôle le 'tray icon', est nécessaire. Vous pouvez forcer ce commutateur de tâche en postant n'importe quel message à la fenêtre du programme. Utilisez juste le simple message 'PostMessage', et non pas 'SendMessage'!

Exemple:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\shell32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\shell32.lib

WM_SHELLNOTIFY equ WM_USER+5
IDI_TRAY equ 0
IDM_RESTORE equ 1000
IDM_EXIT equ 1010
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "TrayIconWinClass",0
AppName db "TrayIcon Demo",0
RestoreString db "&Restore",0
ExitString db "E&xit Program",0

.data?
hInstance dd ?
note NOTIFYICONDATA <>
hPopupMenu dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_DBLCLKS
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL pt:POINT
.if uMsg==WM_CREATE
invoke CreatePopupMenu
mov hPopupMenu,eax
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString
.elseif uMsg==WM_DESTROY
invoke DestroyMenu,hPopupMenu
invoke PostQuitMessage,NULL
.elseif uMsg==WM_SIZE
.if wParam==SIZE_MINIMIZED
mov note.cbSize,sizeof NOTIFYICONDATA
push hWnd
pop note.hwnd
mov note.uID,IDI_TRAY
mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP
mov note.uCallbackMessage,WM_SHELLNOTIFY
invoke LoadIcon,NULL,IDI_WINLOGO
mov note.hIcon,eax
invoke lstrcpy,addr note.szTip,addr AppName
invoke ShowWindow,hWnd,SW_HIDE
invoke Shell_NotifyIcon,NIM_ADD,addr note
.endif
.elseif uMsg==WM_COMMAND
.if lParam==0
invoke Shell_NotifyIcon,NIM_DELETE,addr note
mov eax,wParam
.if ax==IDM_RESTORE
invoke ShowWindow,hWnd,SW_RESTORE
.else
invoke DestroyWindow,hWnd
.endif
.endif
.elseif uMsg==WM_SHELLNOTIFY
.if wParam==IDI_TRAY
.if lParam==WM_RBUTTONDOWN
invoke GetCursorPos,addr pt
invoke SetForegroundWindow,hWnd
invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL
invoke PostMessage,hWnd,WM_NULL,0,0
.elseif lParam==WM_LBUTTONDBLCLK
invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0
.endif
.endif
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp

end start

Analyse:

Notre programme affichera une simple fenêtre. Lorsque vous appuyez sur le bouton de réduction (au minimum), il se cachera et mettra une icône dans le 'system tray' tout en bas à droite à côté de votre horloge. Quand vous double cliquez sur l'icône, le programme se rétablira et enlèvera l'icône du 'system tray'. Quand vous simple-cliquez dessus, un menu popup est affiché. Grâce à ce menu, vous pourrez soit le rétablir (réagrandir) soit en sortir.

.if uMsg==WM_CREATE
invoke CreatePopupMenu
mov hPopupMenu,eax
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString

Quand la fenêtre principale est créée, on crée un menu popup et on y ajouter deux Items (deux sous-menu). La fonction 'AppendMenu' a la syntaxe suivante :

AppendMenu PROTO hMenu:DWORD, uFlags:DWORD, uIDNewItem:DWORD, lpNewItem:DWORD
  • hMenu est l'handle du menu auquel vous voulez ajouter l'Item.
  • uFlags indique à Windows que l'Item du menu qui doit y être ajouté est un bitmap ou une chaîne de caractères ou bien encore un Item possédant d'autres sous-Items, activé (enable), grisé (gray) ou désactivé (disable) etc. Vous pouvez obtenir la liste complète avec la référence api win32. Dans notre exemple, nous employons MF_STRING ce qui signifie que l'Item du menu est une simple chaîne de caractères.
  • uIDNewItem est l'ID de l'Item du menu. C'est une valeur définie par l'utilisateur, elle est employée pour représenter l'Item du menu.
  • lpNewItem est le contenu de l'Item, d'après ce que vous avez spécifié dans le membre uFlags. Puisque nous mettons MF_STRING dans le membre uFlags, lpNewItem doit contenir le pointer de la chaîne de caractères qui doit être affichée dans le menu popup.
Après que le menu popup est été créé, la fenêtre principale attend patiemment que l'utilisateur appuie sur le bouton de réduction (minimum).
Quand une fenêtre est réduite au minimum, elle reçoit le message WM_SIZE avec la valeur SIZE_MINIMIZED dans wParam.

.elseif uMsg==WM_SIZE
.if wParam==SIZE_MINIMIZED
mov note.cbSize,sizeof NOTIFYICONDATA
push hWnd
pop note.hwnd
mov note.uID,IDI_TRAY
mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP
mov note.uCallbackMessage,WM_SHELLNOTIFY
invoke LoadIcon,NULL,IDI_WINLOGO
mov note.hIcon,eax
invoke lstrcpy,addr note.szTip,addr AppName
invoke ShowWindow,hWnd,SW_HIDE
invoke Shell_NotifyIcon,NIM_ADD,addr note
.endif

Nous profitons de cette occasion pour remplir la structure NOTIFYICONDATA. 'IDI_tray' est juste une constante définie au début du code source. Vous pouvez lui donner la valeur que vous souhaitez. Ce n'est pas important puisque on a seulement un seul 'Tray Icon'. Mais si on en avait plusieurs dans notre 'System Tray', on aurait besoin d'IDs uniques pour chaque 'tray icon'. On définit tous les flags suivants dans le membre uFlags parce que nous spécifions une icône (NIF_ICON), nous spécifions un (message sur mesure NIF_MESSAGE et nous spécifions le texte tooltip (NIF_TIP) (Le texte qui est pésenté dans une bulle). WM_SHELLNOTIFY est seulement un message sur mesure (custum) défini comme WM_USER+5. La valeur réelle n'est pas importante tant qu'elle reste unique. Ici, j'emploie l'icône winlogo comme 'tray icon' mais vous pouvez employer n'importe quelle icône dans votre programme. Chargez-le juste en ressource avec LoadIcon et mettez l'handle renvoyé dans le membre hIcon. Finalement, nous mettons (dans szTip) le texte que nous voulons que la bulle affiche lorsque la souris passe au dessus de notre icône.
Nous cachons la fenêtre principale pour donner l'illusion de sa disparition en la "réduisant au minimum en une icône du 'System Tray'".
Ensuite nous appelons Shell_NotifyIcon avec le message NIM_ADD pour ajouter une icône au system tray.

Maintenant notre fenêtre principale est cachée et l'icône est réduite dans le 'System Tray'. Si vous passez la souris au-dessus, vous verrez un tooltip (une bulle) qui montre le texte que nous avons mis dans le membre szTip. Ensuite, si vous double cliquez sur l'icône, la fenêtre principale réapparaîtra et le 'tray icon' disparaîtra.

.elseif uMsg==WM_SHELLNOTIFY
.if wParam==IDI_TRAY
.if lParam==WM_RBUTTONDOWN
invoke GetCursorPos,addr pt
invoke SetForegroundWindow,hWnd
invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL
invoke PostMessage,hWnd,WM_NULL,0,0
.elseif lParam==WM_LBUTTONDBLCLK
invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0
.endif
.endif

Quand un événement souris arrive au tray icon (un clique de souris sur notre icône), notre fenêtre reçoit le message WM_SHELLNOTIFY qui est le message fait sur mesure que nous avons déclaré dans le membre uCallbackMessage. Rappelons-nous qu'à la réception de ce message, wParam contient l'ID du 'tray icon' et lParam contient le message actuel de la souris. Dans le code ci-dessus, nous vérifions d'abord si ce message vient du 'tray icon' auquel nous nous intéressons. Si c'est le cas, on vérifie le message actuel de la souris. Puisque nous sommes uniquement intéressés au 'clique droit de la souris' et au "double clique gauche", on s'occupe seulement des messages WM_RBUTTONDOWN et WM_LBUTTONDBLCLK.
Si le message de la souris est WM_RBUTTONDOWN, nous appelons GetCursorPos pour obtenir les coordonnées actuelles du curseur de la souris sur l'écran. Après avoir reçu les données renvoyées par cette fonction, la structure POINT est remplie des coordonnées du curseur de la souris. Par les coordonnées, je veux dire les coordonnées entières sans qu'il n'y ait aucune frontière de fenêtre (on parle vraiment de l'écran dans sa totalité). Par exemple, si la résolution de votre écran est 640*480, le coin 'inférieur droit' de l'écran est x = 639 et y = 479. Si vous voulez convertir les coordonnées de l'écran en coordonnées de fenêtre, utilisez la fonction ScreenToClient.
Cependant, pour notre but, nous voulons afficher le menu popup à la position actuelle du curseur de la souris avec l'appel TrackPopupMenu et cette fonction exige des coordonnées d'écran, nous pouvons donc directement utiliser les coordonnées remplies par GetCursorPos.
TrackPopupMenu a la syntaxe suivante :

    TrackPopupMenu PROTO hMenu:DWORD, uFlags:DWORD, x:DWORD, y:DWORD, nReserved:DWORD, hWnd:DWORD, prcRect:DWORD
  • hMenu est l'handle du menu popup être affiché.
  • uFlags indique les options de la fonction. Comme l'endroit où placer le menu par rapport aux coordonnées spécifiées plus tard ou bien quel bouton de la souris sera employé pour montrer le menu. Dans notre exemple, nous employons TPM_RIGHTALIGN pour placer le menu popup à gauche des coordonnées.
  • x et y indiquent l'emplacement du menu (en coordonnées d'écran).
  • nReserved doit être NULL
  • hWnd est l'handle de la fenêtre qui recevra les messages du menu.
  • prcRect est un rectangle sur l'écran dans lequel il est possible de cliquer sans écarter le menu. Normalement nous mettons un NULL ici, comme ça lorsque l'utilisateur clique où que ce soit à l'extérieur du menu popup, le menu est écarté (déplacé).
Quand l'utilisateur double clique sur le 'tray icon', on envoie un message WM_COMMAND à notre propre fenêtre en spécifiant IDM_RESTORE pour imiter une sélection faite par l'utilisateur pour 'restorer'(réagrandir) la fenêtre principale et enlever l'icône (de notre programme) du 'System Tray'. Pour être capable de recevoir le message de double-clic, la fenêtre principale doit avoir le style CS_DBLCLKS.

invoke Shell_NotifyIcon,NIM_DELETE,addr note
mov eax,wParam
.if ax==IDM_RESTORE
invoke ShowWindow,hWnd,SW_RESTORE
.else
invoke DestroyWindow,hWnd
.endif

Quand l'utilisateur sélectionne l'Item de 'restoration' du menu, nous enlevons le 'tray icon' en appelant à nouveau Shell_NotifyIcon, cette fois nous spécifions NIM_DELETE comme message. Ensuite, nous rétablissons la fenêtre principale à son état d'origine. Si l'utilisateur choisit l'Item 'de Sortie' du menu, nous enlevons aussi l'icône du 'System tray' et détruisons la fenêtre principale en appelant DestroyWindow.

[Iczelion's Win32 Assembly Homepage]
Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 24: Windows Hooks

Tutorial 24: Windows Hooks
(Hook=Crochet)

Nous allons voir ce que sont les Windows Hooks dans ce Tutorial. Les Windows Hooks sont très puissants. Grâce à eux, vous pouvez fouiner à l'intérieur d'autres process (programmes) et quelques fois changer leurs comportements.
Downloadez l'exemple ici.

Théorie:

Les Windows Hooks peuvent être considérés comme une des particularités les plus puissantes de Windows. Avec elles, vous pourrez prendre au piège les événements qui se produisent dans votre propre process ou bien dans d'autres process. (exemple vous verrez apparaître dans votre fenêtre le n° qui sert d'handle à un bouton ou à une boîte se saisie…ça sert à ça un Hook). En "accrochant" (en Hookant), vous informez Windows sur les caractéristiques de la fonction Hookée. Notre fonction de filtre est aussi appelée la procédure d'hook, laquelle sera appelée à chaque fois qu'un événement intéressant se produit. Il y en a deux types en fait : les Hooks locaux et éloignés.
  • Les Hooks locaux prennent au piège les événements qui sont en train de se passer dans votre propre process.
  • Les Hooks éloignés prennent au piège les événements qui sont en train de se passer dans d'autres process. Il y a deux types d'Hooks éloignés.
    • thread-specific (ou lien-spécifique) prend au piège les événements qui se passent dans un lien spécifique dans d'autres process. Bref, il vous sert à observer des événements d'un lien spécifique à l'intérieur d'un process spécifique.
    • system-wide (système-large) prend au piège tous les événements destinés à tous les liens dans tous les process dans le système.
Quand vous mettez en place des Hooks, rappelez-vous qu'ils ont une influence sur les performances du système. Les 'System-Wide Hooks' sont tristement célèbres. Puisque TOUS les événements de liens seront mis en déroute par votre fonction de filtre (votre procédure d'Hooks), votre système peut être relativement ralentir. Ainsi si vous employez un 'System-Wide Hook', vous devez l'utiliser judicieusement et non pas l'accrocher aussitôt même si vous n'en avez pas encore besoin. Donc, vous avez de grandes chances de faire planter les autres process puisque vous pouvez toucher au fonctionnement d'autres process et si il y a quelque chose d'incorrecte dans votre fonction de filtre, il se peut que les autres programmes restent en attente. Souvenez-vous en : 'le pouvoir vient de paire avec la responsabilité'.
Vous devez vraiment comprendre le fonctionnement d'un hook avant que vous ne puissiez l'employer efficacement. Quand vous créez un hook, Windows crée une structure de données dans la mémoire, contenant l'information sur le hook et l'ajoute à une liste liée de Hooks existant. Le nouvel hook est ajouté devant les vieux Hooks. Lorsqu'un événement arrive, si vous faites appel à un hook local, la fonction de filtre de votre programme sera appelée, donc c'est plus direct. Mais si c'est un hook éloigné, le système doit injecter le code pour la procédure d'hook dans l'espace d'adresse d'un autre process. Et le système peut faire ça uniquement si la fonction se trouve dans une DLL. Ainsi, si vous voulez employer un hook éloigné, votre procédure d'hook doit résider dans une DLL. Il y a deux exceptions à cette règle: l'écriture d'un journal et la lecture d'un journal d'Hooks. Les procédures d'hook pour ces deux Hooks doivent résider dans le lien qui met en place les Hooks. La raison pour laquelle ça doit être ainsi c'est que : les deux Hooks fonctionnent avec des événements de type interruptions d'entrée matériel de bas niveau. Les événements d'entrée doivent être (recorded/playbacked) enregistrés/lus dans l'ordre où ils sont apparus. Si le code de ces deux Hooks sont dans une DLL, les événements d'entrée peuvent se disperser parmi plusieurs liens et il est impossible de connaître leur ordre les uns par rapport aux autres. Donc la solution c'est que : la procédure d'hook de ce deux Hooks doit être dans un lien simple seulement c'est-à-dire le lien qui installe les Hooks.
Il y a 14 types d'Hooks :
  • WH_CALLWNDPROC est appelé quand SendMessage est appelé
  • WH_CALLWNDPROCRET est appelé lors du retours de SendMessage.
  • WH_GETMESSAGE est appelé quand GetMessage ou PeekMessage sont appelés
  • WH_KEYBOARD est appelé quand GetMessage ou PeekMessage récupèrent le message WM_KEYUP ou WM_KEYDOWN dans la file d'attente (des messages).
  • WH_MOUSE est appelé quand GetMessage ou PeekMessage récupère un message provenant de la souris dans la file d'attente des messages.
  • WH_HARDWARE est appelé quand GetMessage ou PeekMessage récupère certains messages en provenance de votre matériel, mais qui n'ont pas de rapport avec le clavier ou la souris.
  • WH_MSGFILTER est appelé pour une DialogBox, quand son menu ou un scrollbar est sur le point de traiter un message. Cet hook est local. Il a été spécifiquement conçu pour des objets qui ont leurs propres boucles de message internes.
  • WH_SYSMSGFILTER c'est la même chose que pour WH_MSGFILTER mais pour un système-large (system-wide).
  • WH_JOURNALRECORD est appelé quand Windows récupère le message dans la file d'attente d'une entrée matériel.
  • WH_JOURNALPLAYBACK est appelé quand un événement est requit dans la file d'attente d'entrée matériel du système.
  • WH_SHELL est appelé quand quelque chose d'intéressant provenant du Sell arrive, comme quand la barre de tâche a besoin de rafraîchir (redessiner) ses boutons.
  • WH_CBT est spécialement employé pour la formation assistée par ordinateur. (CBT)
  • WH_FOREGROUNDIDLE est employé intérieurement par Windows. C'est vraiment très peu utilisé pour applications générales.
  • WH_DEBUG est employé pour 'debugger' la procédure se faisant Hooker.
Maintenant que nous avons pris connaissance de la théorie, nous pouvons nous concentrer sur le fait de savoir comment installer/désinstaller des Hooks.
Pour installer un hook, on appelle la fonction SetWindowsHookEx qui a la syntaxe suivante :
SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORD
  • HookType est une des valeurs inscrites ci-dessus, par exemple,WH_MOUSE, WH_KEYBOARD
  • pHookProc est l'adresse de la procédure d'hook qui sera appelée pour traiter les messages du hook spécifié. Si c'est un hook éloigné, il doit résider dans une DLL. Si ce n'est pas le cas, il doit appartenir à votre prog.
  • hInstance est l'instance handle de la DLL dans laquelle la procédure d'hook réside. Si le hook est local, cette valeur doit être NULL.
  • ThreadID est l'ID du lien sur lequel vous aimeriez installer votre hook pour l'espionner. C'est ce paramètre qui détermine si le hook est local ou éloigné. Si ce paramètre est NULL, Windows interprétera votre hook comme un hook système-large éloigné qui s'attaque à tous les liens, dans le système. Si vous spécifiez l'ID d'un lien de votre propre process (programme), cet hook est local. Si vous spécifiez l'ID d'un autre process, votre hook est un lien-spécifique éloigné. Il y a deux exceptions à cette règle :WH_JOURNALRECORD et WH_JOURNALPLAYBACK sont toujours des 'system-wide hooks' locaux (système-large), lesquels n'ont pas besoin d'être dans une DLL. Et WH_SYSMSGFILTER est toujours un 'system-wide hook' éloigné. En réalité il est identique au hook WH_MSGFILTER avec ThreadID=0 (avec pour ID du lien une valeur NULL).
si l'appel est couronné de succès, il renvoie l'handle du hook dans eax. Si c'est pas le cas, un NULL est renvoyé dans eax. Vous devez sauvegarder l'handle du hook pour pouvoir le 'déhooker' plus tard.
Vous pouvez désinstaller votre hook en appelant UnhookWindowsHookEx lequel accepte qu'un seul paramètre, l'handle du hook que vous voulez désinstaller. Si l'appel réussit, il renvoie une valeur non-nulle dans eax. Sinon, il renvoie le NULL (forcément).
Maintenant que vous savez comment installer/désinstaller des Hooks, nous pouvons examiner la procédure d'hook en elle-même.
La procédure d'hook sera appelée à chaque fois qu'un événement étant associé au type de hook que vous a installé, arrive. Par exemple, si vous installez le hook WH_MOUSE quand un événement de souris arrive, votre procédure d'hook sera appelée. Indépendamment du type de hook que vous avez installé, la procédure d'hook a toujours le prototype suivant :
    HookProc proto nCode:DWORD, wParam:DWORD, lParam:DWORD
    • nCode indique le code du hook.
    • wParam et lParam contiennent une information complémentaire à propos de l'événement.
HookProc est en réalité le détenteur du nom de la fonction. Vous pouvez le nommer comme ça vous chante tant qu'il a le prototype vu ci-dessus. L'interprétation de nCode, wParam et lParam dépend du type de hook que vous avez installé. Donc de la valeur de retour de la procédure d'hook. Par exemple :
WH_CALLWNDPROC
  • nCode peut seulement être HC_ACTION ce qui signifie qu'il y a un message envoyé à une fenêtre.
  • wParam contient le message qui vient d'être envoyé, si ce n'est pas le cas il prendra la valeur zéro.
  • lParam pointe sur la structure CWPSTRUCT.
  • La valeur de retour: si on ne l'emploie pas , on gardera un retour NULL.
WH_MOUSE
  • nCode peut être soit HC_ACTION soit HC_NOREMOVE.
  • wParam contient le message de la souris.
  • lParam pointe sur la structure MOUSEHOOKSTRUCT.
  • La valeur de retour: est '0' si le message doit être traité. Et '1' si le message doit être abandonné.
Vous devez consulter votre référence win32 api pour de plus amples détails à propos de la signification des paramètres et de la valeur de retour des hooks que vous souhaitez installer.
Maintenant il reste un petit truc à comprendre à propos de la procédure d'hook. Rappelez-vous que votre Hook est mis à la suite et en tête de la liste des hooks installés les plus récemment (dans une liste liée à votre prog). Quand un événement arrive, Windows appellera uniquement le premier hook de la liste. C'est le devoir de votre procédure d'hook d'appeler le hook suivant dans la liste. Vous pouvez choisir de ne pas appeler le hook suivant mais vous, mieux que quiconque, savez ce que vous faites. La plupart du temps, c'est une bonne chose d'appeler la procédure suivante, comme ça les autres Hooks sont prêts. Vous pouvez appeler le hook suivant en appelant CallNextHookEx lequel possède le prototype suivant :
CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORD
  • hHook est l'handle de votre propre hook. La fonction emploie cet handle pour fouiller la liste et chercher la procédure d'hook qu'elle doit appeler ensuite.
  • nCode, wParam et lParam sont seulement trois valeurs que vous avez la possibilité de passer à d'autres fonctions. On les reçoit après l'appel: CallNextHookEx.
Un point important sur les Hooks éloignés : la procédure d'hook doit résider dans une DLL laquelle sera 'mappée' (configurée) dans d'autres process. (Voir le tutorial 13). Lorsque Windows 'mappe' la DLL dans d'autres process, il ne 'mappera' pas la section des données à l'intérieur de ces autres process. Bref, tous les process (les autres programmes) partagent une unique copie du code mais ils auront leur propre copie privée de la section des données de la DLL! Ça peut être une surprise de taille pour les imprudents. Vous pouvez penser que lorsque vous stockez une valeur dans une variable appartenant à la section des données de la DLL, que cette valeur sera partagée entres chacun des process qui font appel à cette DLL., mais ce n'est pas tout à fait vrai. Dans une situation normale, ça se passe bien comme ça puisque on a l'illusion que chaque process a sa propre copie de la DLL. Mais ce n'est pas le cas quand hook de Windows est concerné. Nous souhaitons que la DLL soit identique pour tous les process, incluant les données. La solution : on doit marquer la section des données en tant que section partagée. On peut faire ça en spécifiant l'attribut de section dans le commutateur de linker. Pour MASM, vous avez besoin d'employer ce commutateur :
/SECTION:<section name>, S
Le nom de la section des données initialisées est .data et les données non initialisées sont .bss. Par exemple si vous voulez assembler une DLL qui contient une procédure d'hook et que vous voulez que la section des données non initialisée soit partagée entre plusieurs process, vous devez employer la ligne suivante :
link /section:.bss,S /DLL /SUBSYSTEM:WINDOWS ..........
L'attribut 'S' marque la section en tant que section partagée.

Exemple:

En fait il faut deux modules : Il y a le programme principal qui affichera les éléments graphiques du GUI (comme d'habitude, ex : les boutons la zone de saisie…) et l'autre partie c'est la DLL qui installera/désinstallera le hook.

;--------------------------------------------- Voici le code source du programme principal --------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include mousehook.inc
includelib mousehook.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

wsprintfA proto C :DWORD,:DWORD,:VARARG
wsprintf TEXTEQU <wsprintfA>

.const
IDD_MAINDLG equ 101
IDC_CLASSNAME equ 1000
IDC_HANDLE equ 1001
IDC_WNDPROC equ 1002
IDC_HOOK equ 1004
IDC_EXIT equ 1005
WM_MOUSEHOOK equ WM_USER+6

DlgFunc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
HookFlag dd FALSE
HookText db "&Hook",0
UnhookText db "&Unhook",0
template db "%lx",0

.data?
hInstance dd ?
hHook dd ?
.code
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,hInstance,IDD_MAINDLG,NULL,addr DlgFunc,NULL
invoke ExitProcess,NULL

DlgFunc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
LOCAL hLib:DWORD
LOCAL buffer[128]:byte
LOCAL buffer1[128]:byte
LOCAL rect:RECT
.if uMsg==WM_CLOSE
.if HookFlag==TRUE
invoke UninstallHook
.endif
invoke EndDialog,hDlg,NULL
.elseif uMsg==WM_INITDIALOG
invoke GetWindowRect,hDlg,addr rect
invoke SetWindowPos, hDlg, HWND_TOPMOST, rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW
.elseif uMsg==WM_MOUSEHOOK
invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128
invoke wsprintf,addr buffer,addr template,wParam
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128
invoke GetClassName,wParam,addr buffer,128
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128
invoke GetClassLong,wParam,GCL_WNDPROC
invoke wsprintf,addr buffer,addr template,eax
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer
.endif
.elseif uMsg==WM_COMMAND
.if lParam!=0
mov eax,wParam
mov edx,eax
shr edx,16
.if dx==BN_CLICKED
.if ax==IDC_EXIT
invoke SendMessage,hDlg,WM_CLOSE,0,0
.else
.if HookFlag==FALSE
invoke InstallHook,hDlg
.if eax!=NULL
mov HookFlag,TRUE
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
.endif
.else
invoke UninstallHook
invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText
mov HookFlag,FALSE
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL
invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL
invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL
.endif
.endif
.endif
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
DlgFunc endp

end start

;--------------------------------------------------- Et voici enfin le code source de la DLL ------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib

.const
WM_MOUSEHOOK equ WM_USER+6

.data
hInstance dd 0

.data?
hHook dd ?
hWnd dd ?

.code
DllEntry proc hInst:HINSTANCE, reason:DWORD, reserved1:DWORD
.if reason==DLL_PROCESS_ATTACH
push hInst
pop hInstance
.endif
mov eax,TRUE
ret
DllEntry Endp

MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
invoke CallNextHookEx,hHook,nCode,wParam,lParam
mov edx,lParam
assume edx:PTR MOUSEHOOKSTRUCT
invoke WindowFromPoint,[edx].pt.x,[edx].pt.y
invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0
assume edx:nothing
xor eax,eax
ret
MouseProc endp

InstallHook proc hwnd:DWORD
push hwnd
pop hWnd
invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
mov hHook,eax
ret
InstallHook endp

UninstallHook proc
invoke UnhookWindowsHookEx,hHook
ret
UninstallHook endp

End DllEntry

;--------------------------------C'est le makefile de la DLL (le moyen de l'assembler)------------------------------

NAME=mousehook
$(NAME).dll: $(NAME).obj
Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm\lib $(NAME).obj
$(NAME).obj: $(NAME).asm
ml /c /coff /Cp $(NAME).asm

Analyse:

Cet exemple ouvrira une boîte de dialogue avec trois commandes d'éditions dans lesquels seront affichés : le nom de classe (Class Name), l'handle de la fenêtre (ou de l'objet) et l'adresse de la procédure de fenêtre (Window Proc). Ces 3 paramètres n'appartiendront pas forcément à votre propre programme, mais plus généralement à n'importe quel objet de n'importe quelle fenêtre sur laquelle passe votre curseur de souris. Il y a deux boutons, 'hook' et 'Exit'. Quand vous appuyez sur le bouton 'hook', le programme Hooke l'entrée souris, et le texte du bouton se modifie en 'Unhook'. Quand vous déplacez le curseur de la souris vers une autre fenêtre ou sur un objet quelconque, les renseignements de cette fenêtre seront affichés dans votre fenêtre principale (votre DialogBox). Lorsque vous appuyez sur le bouton 'Unhook', le programme arrête de hooker les objets qui se trouvent sous la souris.
Ce programme utilise une boîte de dialogue en tant que fenêtre principale. On définit un message construit sur mesure (Custum) WM_MOUSEHOOK, lequel sera employé entre le programme principal et sa DLL, laquelle contient la fonction d'hook. Lorsque le programme principal reçoit ce message, wParam contient l'handle de la fenêtre (ou de l'objet) sur lequel votre curseur de souris est placé. Bien sûr, c'est un choix arbitraire. J'ai décidé d'envoyer la valeur de l'handle dans wParam pour plus de simplicité. Mais vous pouvez très bien choisir votre propre méthode de communication entre le programme principal et la DLL (contenant le hook).

.if HookFlag==FALSE
invoke InstallHook,hDlg
.if eax!=NULL
mov HookFlag,TRUE
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
.endif

Le programme maintient un 'Flag' nommé HookFlag, pour rendre compte de l'état du hook. S'il est 'FALSE'(faux) alors c'est que le hook n'est pas activé. Au contraire s'il est TRUE(vrai) c'est qu'il est actif.
Quand l'utilisateur appuie sur le bouton 'hook', le programme vérifie s'il est déjà installé (actif). Si ce n'est pas le cas, on appelle la fonction InstallHook du hook de la DLL pour l'installer. Notez que nous passons l'handle de notre dialogBox principale en tant que paramètre de la fonction pour que la DLL (contenant la fonction d'hook) puisse envoyer les différents messages WM_MOUSEHOOK vers la bonne fenêtre, c'est-à-dire la nôtre.
A partir du moment où le programme est chargé, la DLL est chargée elle aussi. En réalité, les DLLs sont immédiatement chargées après que le programme ait été mis en mémoire. L'entrypoint de la DLL est appelé avant même que la première instruction du programme principal ne soit exécuté. (EntryPoint=première ligne de code lue dans un programme=début) Ainsi quand le programme principal se met en route, la(les) DLL(s) est/sont déjà initialisée(s). Nous écrivons le code suivant pour l'entrypoint de la DLL :

.if reason==DLL_PROCESS_ATTACH
push hInst
pop hInstance
.endif

Ce code sauvegarde juste l'instance handle de la DLL dans une variable globale nommée hInstance pour pouvoir l'utiliser dans la fonction InstallHook. Puisque l'entrypoint de la DLL est appelé avant les autres fonctions dans la DLL, l'hInstance est toujours valable. On définit hInstance dans la section .data pour que cette valeur soit prête "pour le process". la DLL est 'mappée'(configurée ou temporairement recopiée en mémoire) dans le process, avant même que le curseur de souris ne plane au-dessus des fenêtres ou objets qu'elle doit hooker. Imaginez-vous que par hasard il y ait déjà une DLL qui occupe l'adresse de chargement destinée au DLL (contenant notre hook), alors dans ce cas la DLL serait reconfigurée à une autre adresse. La valeur de 'hInstance' serait remise à jour en prenant en compte la nouvelle adresse de chargement. Lorsque l'utilisateur presse le bouton 'Unhook' puis par la suite le bouton 'Hook', SetWindowsHookEx sera de nouveau appelé. Cependant, cette fois, il utilisera la nouvelle adresse de chargement en tant qu' instance handle mais qui malheureusement sera faux puisque dans notre programme d'exemple, l'adresse de chargement de la DLL n'aura pas été changée. Le Hook sera local, c'est pourquoi vous pourrez uniquement hooker les événements de la souris qui arriveront dans votre propre fenêtre. À peine désirable.

InstallHook proc hwnd:DWORD
push hwnd
pop hWnd
invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
mov hHook,eax
ret
InstallHook endp

La fonction InstallHook est très simple en elle-même. Elle sauvegarde l'handle de la fenêtre dans une variable globale nommée hWnd pour l'utiliser plus tard. On appelle alors SetWindowsHookEx pour installer (activer) le hook sous la souris. La valeur de retour de SetWindowsHookEx est stockée dans une variable globale nommée hHook pour l'utiliser avec UnhookWindowsHookEx au moment où on souhaite désactiver ce hook.
Après qu'on ait appelé SetWindowsHookEx, le hook sous la souris est fonctionnel. Chaque fois qu'un événement souris arrive dans le système, MouseProc (votre procédure d'hook) est appelé.

MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
invoke CallNextHookEx,hHook,nCode,wParam,lParam
mov edx,lParam
assume edx:PTR MOUSEHOOKSTRUCT
invoke WindowFromPoint,[edx].pt.x,[edx].pt.y
invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0
assume edx:nothing
xor eax,eax
ret
MouseProc endp

La première chose à faire, c'est d'appeler CallNextHookEx pour donner à d'autres Hooks la chance de traiter de nouveaux événements souris. Après ça, on appelle la fonction WindowFromPoint pour retrouver l'handle de la fenêtre (ou de l'objet) auxquelles sont indiquées les coordonnée (écran) de la souris. Remarquez que nous employons POINT dans la structure MOUSEHOOKSTRUCT représenté par lParam en tant que coordonnées actuelles de la souris. Après ça nous envoyons l'handle de la fenêtre (ou de l'objet) au programme principal via PostMessage avec le message WM_MOUSEHOOK. Une chose à se rappeler, c'est que : vous ne devez pas employer SendMessage à l'intérieur de la procédure d'hook, ceci peut provoquer une impasse pour les messages. Je recommande PostMessage. La structure MOUSEHOOKSTRUCT est définie ainsi:

MOUSEHOOKSTRUCT STRUCT DWORD
pt POINT <>
hwnd DWORD ?
wHitTestCode DWORD ?
dwExtraInfo DWORD ?
MOUSEHOOKSTRUCT ENDS

  • 'pt' représente les coordonnées (coordonnées écran) actuelles du curseur de la souris.
  • hwnd est l'handle de la fenêtre qui recevra le message souris. En général, c'est la fenêtre qui se trouve sous le curseur de la souris, mais pas toujours. Si une fenêtre appelle SetCapture, l'entrée souris sera redirigée spécialement vers cette fenêtre au lieu de celle sur laquelle passe notre souris. À cause de ça, je n'utilise pas le membre hwnd de cette structure, mais par contre j'appelle WindowFromPoint à la place.
  • wHitTestCode représente une valeur de coup d'essai. La valeur de coup d'essai donne plus d'information sur la position actuelle du curseur de la souris. Il indique dans quelle partie de la fenêtre est ce que le curseur souris se trouve. Pour la liste complète, vérifiez votre référence api win32 avec le message WM_NCHITTEST.
  • dwExtraInfo contient l'information supplémentaire associée au message. Normalement cette valeur est créée à partir de l'appel mouse_event et retrouvée en appelant GetMessageExtraInfo.
Quand la fenêtre principale reçoit le message WM_MOUSEHOOK, on emploie l'handle de la fenêtre placé dans wParam pour retrouver l'information de la fenêtre.

.elseif uMsg==WM_MOUSEHOOK
invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128
invoke wsprintf,addr buffer,addr template,wParam
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128
invoke GetClassName,wParam,addr buffer,128
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128
invoke GetClassLong,wParam,GCL_WNDPROC
invoke wsprintf,addr buffer,addr template,eax
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer
.endif

Pour éviter des scintillements, nous vérifions le texte qui est déjà inscrit dans les commandes d'édition avec le texte que nous souhaitons placer à leur place. s'ils sont identiques, on saute par dessus l'instruction de réécriture.
Nous retrouvons le nom de classe (la Class Name) en appelant GetClassName, et l'adresse de la procédure de fenêtre (La Window Proc) en appelant GetClassLong avec GCL_WNDPROC puis les mettons ensuite sous forme de chaînes de caractères pour les placer dans les contrôles d'édition appropriés.

invoke UninstallHook
invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText
mov HookFlag,FALSE
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL
invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL
invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL

Quand l'utilisateur appuie sur le bouton 'Unhook', le programme appelle la fonction UninstallHook qui se trouve dans la DLL (avec la fonction hook). UninstallHook appelle uniquement UnhookWindowsHookEx. Après ça, le texte du bouton se retransforme en "Hook", le flaf HookFlag est remis à 'False' et on bloque le contenu des contrôles d'édition.
Notez comment est décrit le linker dans le makefile.

Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS

La section .bss est définie comme une section partagée pour faire en sorte que plusieurs process (programmes) puissent utiliser les données non initialisée de cette DLL en même temps. Sans ce commutateur, votre DLL ne fonctionnerait pas correctement.

[Iczelion's Win32 Assembly Homepage]

Traduit pas Morgatte


Iczelion's Win32asm Tutorial 25: Simple Bitmap

Tutorial 25: Un Simple Bitmap

Dans ce tutorial, nous allons voir comment utiliser des bitmaps dans notre programme. Pour être exact, nous allons apprendre à afficher un bitmap dans le secteur client de notre fenêtre.
Downloadez l'exemple.

Théorie

Les simples sont de simples images stockées dans l'ordinateur. Il y a beaucoup de formats d'images utilisés pour les ordinateurs, mais à l'origine Windows ne traitait qu'uniquement les fichiers Graphiques (.bmp). Les Bitmaps dont nous allons parler dans ce tutorial sont des bitmap Windows *.bmp. La plus simple façon d'utiliser un bitmap est de l'employer en tant que ressource. Il y a deux façons pour faire ça. Vous pouvez inclure le bitmap dans un fichier de définition de ressource (.rc) comme suit :
#define IDB_MYBITMAP 100
IDB_MYBITMAP BITMAP "c:\project\example.bmp"
Cette méthode emploie une constante pour représenter le bitmap. La première ligne crée juste une constante nommé IDB_MYBITMAP qui pour valeur 100. Nous utiliserons ce label (ce nom) pour nous référer à ce bitmap dans le programme. La ligne suivante déclare un bitmap ressource. On indique au compilateur de ressource à quel endroit retrouver le fichier bmp réel.
L'autre méthode utilise un nom pour représenter le bitmap comme suit :
MyBitMap BITMAP "c:\project\example.bmp"
Cette méthode exige que vous vous référiez au bitmap dans votre programme grâce au nom "MyBitMap" au lieu d'une valeur.
L'unes ou l'autres de ces méthodes restent excellentes tant que vous sachiez bien quelle méthode vous employez.
Maintenant que nous avons placé le bitmap dans le fichier ressource, nous pouvons continuer les étapes de l'explication pour afficher ce bitmap dans le secteur client de notre fenêtre.
  1. Appelez LoadBitmap pour obtenir l'handle du bitmap. LoadBitmap possède le prototype de fonction suivant :
      LoadBitmap proto hInstance:HINSTANCE, lpBitmapName:LPSTR

    Cette fonction renvoie l'handle du bitmap. hInstance est l'instance handle de notre programme. LpBitmapName est un pointer qui est sur la châine de caractères qui définit le nom du bitmap (dans le cas où vous employez la deuxième méthode pour se référer au bitmap). Si vous employez une constante pour vous référer au bitmap (comme IDB_MYBITMAP), vous pouvez mettre sa valeur ici. (Dans l'exemple au-dessus, ce serait 100). Voici un court exemple:

      First Method:

      .386
      .model flat, stdcall
      ................
      .const
      IDB_MYBITMAP equ 100
      ...............
      .data?
      hInstance dd ?
      ..............
      .code
      .............
      invoke GetModuleHandle,NULL
      mov hInstance,eax
      ............
      invoke LoadBitmap,hInstance,IDB_MYBITMAP
      ...........

      Second Method:

      .386
      .model flat, stdcall
      ................
      .data
      BitmapName db "MyBitMap",0
      ...............
      .data?
      hInstance dd ?
      ..............
      .code
      .............
      invoke GetModuleHandle,NULL
      mov hInstance,eax
      ............
      invoke LoadBitmap,hInstance,addr BitmapName
      ...........

    Pour info: un 'contexte de dispositif' c'est en quelque sorte la zone blanche principale de votre fenêtre sur laquelle on affichera le dessin bitmap. Elle est réelle quand on parle d'une page physiquement afficher sur notre écran. Mais on peut aussi se le réprésenter comme une page fictive n'exsitant que sous forme de données en mémoire.

  1. Récupérez l'handle du contexte de dispositif (DC=device context ). Vous pouvez obtenir cet handle en appelant BeginPaint après le retour du message WM_PAINT ou en appelant GetDC à n'importe quel moment.
  2. Créez un contexte de dispositif en mémoire qui a le même attribut que le contexte de dispositif que nous venons juste d'obtenir. L'idée ici est de créer une sorte de feuille de dessin "cachée" sur laquelle nous pouvons dessiner le bitmap. Quand nous avons finis cette opération, il nous suffit de copier le contenu de la feuille de dessin (cachée) sur le contexte de dispositif réel, grâce à un appel de fonction. C'est un exemple technique de double-affichage, utilisé pour une rapide exposition d'images à l'écran. Vous pouvez créer cette feuille de dessin "cachée" en appelant CreateCompatibleDC.
      CreateCompatibleDC proto hdc:HDC

    Si cette fonction réussit, elle renvoie l'handle du contexte de dispositif de mémoire dans eax. hdc est l'handle du contexte de dispositif pour lequel vous souhaitez que la mémoire DC soit compatible avec lui.

  1. Maintenant que vous avez obtenu une feuille de dessin cachée, vous pouvez dessiner dessus en choisissant le bitmap. On fait ça en appelant SelectObject avec l'handle de la mémoire DC en tant que premier paramètre et l'handle de bitmap comme deuxième paramètre. SelectObject est définit ainsi :
      SelectObject proto hdc:HDC, hGdiObject:DWORD
  1. Le bitmap est dessiné sur le contexte de dispositif en mémoire maintenant. Tout ce que nous avons besoin de faire ici, c'est de le recopier sur le dispositif d'affichage réel, à savoir (qui est) le vrai contexte de dispositif. Il y a plusieurs fonctions qui peuvent exécuter cette opération comme BitBlt et StretchBlt. BitBlt recopie seulement le contenu d'un DC dans un autre donc c'est rapide, tandis que StretchBlt peut décompresser ou compresser le bitmap pour le redimensionner à la taille du secteur d'affichage de votre fenêtre. Ici, nous emploierons BitBlt pour simplifier. BitBlt a la définition suivante :
      BitBlt proto hdcDest:DWORD, nxDest:DWORD, nyDest:DWORD, nWidth:DWORD, nHeight:DWORD, hdcSrc:DWORD, nxSrc:DWORD, nySrc:DWORD, dwROP:DWORD
    hdcDest est l'handle du contexte de dispositif réel qui sert de destination pour l'opération de transfert du bitmap.
    nxDest, nyDest sont les coordonnées du coin supérieur gauche du secteur d'affichage.
    nWidth, nHeight sont la largeur et la hauteur du secteur d'affichage.
    hdcSrc est l'handle du contexte de dispositif qui sert de source dans l'opération de transfert du bitmap.
    nxSrc, nySrc sont les coordonnées du coin supérieur gauche du rectangle source.
    dwROP est le code de l'opération qui permet d'appliquer au mieux les données de couleur du bitmap avec les données de couleur mis à disposition par le secteur d'affichage, pour réaliser le résultat final. La plupart du temps, on souhaite seulement recopier les données de couleur existantes dans le nouveau.
  1. Quand vous avez affiché le bitmap, vous pouvez le supprimer avec l'appel d'API DeleteObject.
Voilà! Pour récapituler, vous avez besoin de placer le bitmap dans un script de ressource (voir plus bas, après la source du programme). Chargez-le alors en ressource avec LoadBitmap. Vous obtiendrez l'handle du bitmap. Ensuite vous obtenez l'handle du contexte de dispositif du secteur sur lequel vous souhaitez redessiner le bitmap. Alors vous créez un contexte de dispositif en mémoire qui est compatible avec le contexte de dispositif que vous venez juste d'obtenir. Choisissez le bitmap dans la mémoire DC et recopiez alors le contenu de la mémoire DC vers votre DC réel.

Example Code:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
IDB_MAIN   equ 1

.data
ClassName db "SimpleWin32ASMBitmapClass",0
AppName  db "Win32ASM Simple Bitmap Example",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hBitmap dd ?

.code
start:
 invoke GetModuleHandle, NULL
 mov    hInstance,eax
 invoke GetCommandLine
 mov    CommandLine,eax
 invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
 invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
 LOCAL wc:WNDCLASSEX
 LOCAL msg:MSG
 LOCAL hwnd:HWND
 mov   wc.cbSize,SIZEOF WNDCLASSEX
 mov   wc.style, CS_HREDRAW or CS_VREDRAW
 mov   wc.lpfnWndProc, OFFSET WndProc
 mov   wc.cbClsExtra,NULL
 mov   wc.cbWndExtra,NULL
 push  hInstance
 pop   wc.hInstance
 mov   wc.hbrBackground,COLOR_WINDOW+1
 mov   wc.lpszMenuName,NULL
 mov   wc.lpszClassName,OFFSET ClassName
 invoke LoadIcon,NULL,IDI_APPLICATION
 mov   wc.hIcon,eax
 mov   wc.hIconSm,eax
 invoke LoadCursor,NULL,IDC_ARROW
 mov   wc.hCursor,eax
 invoke RegisterClassEx, addr wc
 INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
           hInst,NULL
 mov   hwnd,eax
 invoke ShowWindow, hwnd,SW_SHOWNORMAL
 invoke UpdateWindow, hwnd
 .while TRUE
  invoke GetMessage, ADDR msg,NULL,0,0
  .break .if (!eax)
  invoke TranslateMessage, ADDR msg
  invoke DispatchMessage, ADDR msg
 .endw
 mov     eax,msg.wParam
 ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
   LOCAL ps:PAINTSTRUCT
   LOCAL hdc:HDC
   LOCAL hMemDC:HDC
   LOCAL rect:RECT
   .if uMsg==WM_CREATE
      invoke LoadBitmap,hInstance,IDB_MAIN
      mov hBitmap,eax
   .elseif uMsg==WM_PAINT
      invoke BeginPaint,hWnd,addr ps
      mov    hdc,eax
      invoke CreateCompatibleDC,hdc
      mov    hMemDC,eax
      invoke SelectObject,hMemDC,hBitmap
      invoke GetClientRect,hWnd,addr rect
      invoke BitBlt,hdc,0,0,rect.right,rect.bottom,hMemDC,0,0,SRCCOPY
      invoke DeleteDC,hMemDC
      invoke EndPaint,hWnd,addr ps
 .elseif uMsg==WM_DESTROY
  invoke DeleteObject,hBitmap
  invoke PostQuitMessage,NULL
 .ELSE
  invoke DefWindowProc,hWnd,uMsg,wParam,lParam
  ret
 .ENDIF
 xor eax,eax
 ret
WndProc endp
end start

;---------------------------------------------------------------------
;                            le script resource
;---------------------------------------------------------------------
#define IDB_MAIN 1
IDB_MAIN BITMAP "tweety78.bmp"

Analyse:

Il n'y a pas grand chose à analyser dans ce tutorial ;)
#define IDB_MAIN 1
IDB_MAIN BITMAP "tweety78.bmp"
Ça définit la constante nommée IDB_MAIN, on lui associe la valeur 1. Et on emploie ensuite cette constante en tant qu'identificateur du bitmap en ressource. Le fichier bitmap qui est inclus dans la ressource est "tweety78.bmp" lequel réside dans le même dossier que le script de ressource.

   .if uMsg==WM_CREATE
      invoke LoadBitmap,hInstance,IDB_MAIN
      mov hBitmap,eax

En réponse à WM_CREATE, nous appelons LoadBitmap pour charger le bitmap en ressource, en passant l'identificateur du bitmap en ressource comme deuxième paramètre de l'API. Nous obtenons l'handle du bitmap au moment du retour de la fonction.
Maintenant que le bitmap est chargé, nous pouvons le dessiner dans le secteur client de notre fenêtre principale.

   .elseif uMsg==WM_PAINT
      invoke BeginPaint,hWnd,addr ps
      mov    hdc,eax
      invoke CreateCompatibleDC,hdc
      mov    hMemDC,eax
      invoke SelectObject,hMemDC,hBitmap
      invoke GetClientRect,hWnd,addr rect
      invoke BitBlt,hdc,0,0,rect.right,rect.bottom,hMemDC,0,0,SRCCOPY
      invoke DeleteDC,hMemDC
      invoke EndPaint,hWnd,addr ps

Nous décidons de repeindre le bitmap après la réponse du message WM_PAINT. Nous appelons d'abord BeginPaint pour obtenir l'handle du contexte de dispositif. Ensuite, nous créons une mémoire compatible DC avec CreateCompatibleDC. Puis on sélectionne le bitmap à l'intérieur de la mémoire DC grâce à SelectObject. Déterminez la dimension de votre secteur client avec GetClientRect. Maintenant nous pouvons faire apparaître le bitmap dans le secteur client en appelant BitBlt, lequel recopie le bitmap de la mémoire DC vers le DC réel. Lorsque la repeinte est effectue, nous n'avons plus besoin de la mémoire DC donc nous le supprimons grâce à DeleteDC. On fini la session de peinture avec EndPaint.

 .elseif uMsg==WM_DESTROY
  invoke DeleteObject,hBitmap
  invoke PostQuitMessage,NULL
Dès lors que nous n'avons plus besoin du bitmap, nous le supprimons avec DeleteObject.
[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 26: Splash Screen

Tutorial 26: Splash Screen

Maintenant que nous savons comment utiliser un bitmap, nous pouvons progresser en s'en servant d'une manière plus créatrice. Le 'splash screen'. Downloadez l'exemple. Mais en fait cet exemple est zippé et puisqu'il fait appelle à une DLL, il ne fonctionnera correctement qu'en l'ayant dézippé d'abord!

Théorie

Un splash screen est une fenêtre qui n'a aucune barre de titre, aucun système de menu, aucune frontière et qui affiche un bitmap un court instant avant de le faire redisparaître automatiquement. On s'en sert d'habitude au démarrage d'un programme, pour montrer un logo ou pour détourner l'attention de l'utilisateur pendant que le programme rame à cause de longues initialisations. Nous allons donc créer un splash screen dans ce tutorial.
La première étape c'est d'inclure le bitmap dans le fichier de ressource. Cependant, si vous réfléchissez, c'est une perte de mémoire précieuse de charger un bitmap qui ne sera employé qu'une seule fois. Une solution bien meilleure, c'est de créer une sorte de *DLL resource* qui contient le bitmap et qui aura pour unique but d'afficher notre Splash screen. De cette façon, vous pouvez charger la DLL lorsque vous souhaitez afficher le Splash screen puis le décharger quand il n'est plus nécessaire. Donc on aura deux modules : le programme principal et la DLL Splash. Nous plaçons le bitmap dans la DLL ressource.
Le schéma général est comme suit :
  1. Mettez le bitmap dans la DLL comme un bitmap mis en ressource.
  2. Le programme principal appelle LoadLibrary pour charger la DLL en mémoire.
  3. On appelle l'entrypoint de la DLL. On créera un minuteur (un Timer) et nous définiront la durée d'affichage du Splash screen. Ensuite nous enregistrerons et créerons une fenêtre sans titre ni bords puis nous afficherons le bitmap dans son secteur client.
  4. Quand la durée indiquée s'est écoulée, le Splash screen est enlevé de l'écran et le contrôle est rendu au programme principal.
  5. Le programme principal appelle FreeLibrary pour enlever (décharger) la DLL de la mémoire et continue ensuite avec n'importe quelle autre tâche que nous supposons avoir mis à sa suite.
Nous allons examiner les mécanismes en détail.

Chargement/Déchargement de la DLL (Load/Unload)

Vous pouvez charger dynamiquement une DLL grâce à la fonction LoadLibrary qui a la syntaxe suivante :
LoadLibrary proto lpDLLName:DWORD
Elle ne prend seulement qu'un paramètre : l'adresse du nom de la DLL que vous voulez charger en mémoire. Si l'appel est couronné de succès, il renvoie l'handle de la DLL, sinon elle renvoie un NULL.
Pour décharger une DLL, appelez FreeLibrary :
FreeLibrary proto hLib:DWORD
Il ne prend aussi qu'un seul paramètre : l'handle de la DLL que vous souhaitez décharger. Normalement, vous avez obtenu cet handle grâce à LoadLibrary.

Comment employer un 'TIMER' (minuteur) ?

D'abord, vous devez créer un Timer avec SetTimer :
SetTimer proto hWnd:DWORD, TimerID:DWORD, uElapse:DWORD, lpTimerFunc:DWORD

hWnd est le handle de la fenêtre qui recevra le message d'avis du Timer. Ce paramètre peut être NULL pour spécifier qu'il n'y a aucune fenêtre étant associée au Timer.
TimerID est une valeur définie par l'utilisateur, laquelle est employée en tant qu'ID du Timer.
uElapse est la valeur du temps d'attente en millisecondes.
lpTimerFunc est l'adresse de la fonction qui traitera les messages d'avis du Timer. Si vous passez un NULL, les messages du Timer seront envoyés à la fenêtre indiquée par le paramètre hWnd.

SetTimer renvoie l'ID du Timer en cas de succès. Autrement il renvoie un NULL. Donc il est préférable de ne pas mettre l'ID du Timer à 0.

Vous pouvez créer un Timer de deux façons :
  • Si vous avez une fenêtre et que vous souhaitez que les messages d'avis du Timer aillent vers cette fenêtre, alors vous devez passer Les quatre paramètres ci-dessus à SetTimer (le lpTimerFunc doit rester NULL).
  • Si vous n'avez pas encore de fenêtre ou bien que vous ne voulez pas traiter les messages du Timer dans la procédure de fenêtre, vous devez passer un NULL à la fonction à la place de l'handle de la fenêtre. Vous devez aussi définir l'adresse de la fonction du Timer qui traitera les messages du Timer.
Nous emploierons la première approche dans cet exemple.
Quand la période de temps mort s'est écoulée, le message WM_TIMER est envoyé à la fenêtre qui est associée au Timer. Par exemple, si vous spécifiez un uElapse de 1000, votre fenêtre recevra WM_TIMER chaque seconde.
Lorsque vous n'avez plus besoin du Timer, on le détruit avec KillTimer :
KillTimer proto hWnd:DWORD, TimerID:DWORD

Exemple:

;-----------------------------------------------------------------------
;                         Voici le programme principal:
;-----------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "SplashDemoWinClass",0
AppName  db "Splash Screen Example",0
Libname db "splash.dll",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
 invoke LoadLibrary,addr Libname
 .if eax!=NULL
    invoke FreeLibrary,eax
 .endif
 invoke GetModuleHandle, NULL
 mov    hInstance,eax
 invoke GetCommandLine
 mov    CommandLine,eax
 invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
 invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
 LOCAL wc:WNDCLASSEX
 LOCAL msg:MSG
 LOCAL hwnd:HWND
 mov   wc.cbSize,SIZEOF WNDCLASSEX
 mov   wc.style, CS_HREDRAW or CS_VREDRAW
 mov   wc.lpfnWndProc, OFFSET WndProc
 mov   wc.cbClsExtra,NULL
 mov   wc.cbWndExtra,NULL
 push  hInstance
 pop   wc.hInstance
 mov   wc.hbrBackground,COLOR_WINDOW+1
 mov   wc.lpszMenuName,NULL
 mov   wc.lpszClassName,OFFSET ClassName
 invoke LoadIcon,NULL,IDI_APPLICATION
 mov   wc.hIcon,eax
 mov   wc.hIconSm,eax
 invoke LoadCursor,NULL,IDC_ARROW
 mov   wc.hCursor,eax
 invoke RegisterClassEx, addr wc
 INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
           hInst,NULL
 mov   hwnd,eax
 invoke ShowWindow, hwnd,SW_SHOWNORMAL
 invoke UpdateWindow, hwnd
 .while TRUE
  invoke GetMessage, ADDR msg,NULL,0,0
  .break .if (!eax)
  invoke TranslateMessage, ADDR msg
  invoke DispatchMessage, ADDR msg
 .endw
 mov     eax,msg.wParam
 ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
 .IF uMsg==WM_DESTROY
  invoke PostQuitMessage,NULL
 .ELSE
  invoke DefWindowProc,hWnd,uMsg,wParam,lParam
  ret
 .ENDIF
 xor eax,eax
 ret
WndProc endp
end start

;--------------------------------------------------------------------
;                         Et voici la DLL contenant le Bitmap:
;--------------------------------------------------------------------
.386
.model flat, stdcall
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
.data
BitmapName db "MySplashBMP",0
ClassName db "SplashWndClass",0
hBitMap dd 0
TimerID dd 0

.data
hInstance dd ?

.code

DllEntry proc hInst:DWORD, reason:DWORD, reserved1:DWORD
   .if reason==DLL_PROCESS_ATTACH  ; Quand la DLL est chargée.
      push hInst
      pop hInstance
      call ShowBitMap
   .endif
   mov eax,TRUE
   ret
DllEntry Endp
ShowBitMap proc
        LOCAL wc:WNDCLASSEX
        LOCAL msg:MSG
        LOCAL hwnd:HWND
        mov   wc.cbSize,SIZEOF WNDCLASSEX
        mov   wc.style, CS_HREDRAW or CS_VREDRAW
        mov   wc.lpfnWndProc, OFFSET WndProc
        mov   wc.cbClsExtra,NULL
        mov   wc.cbWndExtra,NULL
        push  hInstance
        pop   wc.hInstance
        mov   wc.hbrBackground,COLOR_WINDOW+1
        mov   wc.lpszMenuName,NULL
        mov   wc.lpszClassName,OFFSET ClassName
        invoke LoadIcon,NULL,IDI_APPLICATION
        mov   wc.hIcon,eax
        mov   wc.hIconSm,0
        invoke LoadCursor,NULL,IDC_ARROW
        mov   wc.hCursor,eax
        invoke RegisterClassEx, addr wc
        INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\
           WS_POPUP,CW_USEDEFAULT,\
           CW_USEDEFAULT,250,250,NULL,NULL,\
           hInstance,NULL
        mov   hwnd,eax
        INVOKE ShowWindow, hwnd,SW_SHOWNORMAL
        .WHILE TRUE
                INVOKE GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                INVOKE TranslateMessage, ADDR msg
                INVOKE DispatchMessage, ADDR msg
        .ENDW
        mov     eax,msg.wParam
        ret
ShowBitMap endp
WndProc proc hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
        LOCAL ps:PAINTSTRUCT
        LOCAL hdc:HDC
        LOCAL hMemoryDC:HDC
        LOCAL hOldBmp:DWORD
        LOCAL bitmap:BITMAP
        LOCAL DlgHeight:DWORD
        LOCAL DlgWidth:DWORD
        LOCAL DlgRect:RECT
        LOCAL DesktopRect:RECT

        .if uMsg==WM_DESTROY
                .if hBitMap!=0
                        invoke DeleteObject,hBitMap
                .endif
                invoke PostQuitMessage,NULL
        .elseif uMsg==WM_CREATE
                invoke GetWindowRect,hWnd,addr DlgRect
                invoke GetDesktopWindow
                mov ecx,eax
                invoke GetWindowRect,ecx,addr DesktopRect
                push  0
                mov  eax,DlgRect.bottom
                sub  eax,DlgRect.top
                mov  DlgHeight,eax
                push eax
                mov  eax,DlgRect.right
                sub  eax,DlgRect.left
                mov  DlgWidth,eax
                push eax
                mov  eax,DesktopRect.bottom
                sub  eax,DlgHeight
                shr  eax,1
                push eax
                mov  eax,DesktopRect.right
                sub  eax,DlgWidth
                shr  eax,1
                push eax
                push hWnd
                call MoveWindow
                invoke LoadBitmap,hInstance,addr BitmapName
                mov hBitMap,eax
                invoke SetTimer,hWnd,1,2000,NULL
                mov TimerID,eax
        .elseif uMsg==WM_TIMER
                invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL
                invoke KillTimer,hWnd,TimerID
        .elseif uMsg==WM_PAINT
                invoke BeginPaint,hWnd,addr ps
                mov hdc,eax
                invoke CreateCompatibleDC,hdc
                mov hMemoryDC,eax
                invoke SelectObject,eax,hBitMap
                mov hOldBmp,eax
                invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap
                invoke StretchBlt,hdc,0,0,250,250,\
                       hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY
                invoke SelectObject,hMemoryDC,hOldBmp
                invoke DeleteDC,hMemoryDC
                invoke EndPaint,hWnd,addr ps
        .elseif uMsg==WM_LBUTTONDOWN
                invoke DestroyWindow,hWnd
        .else
                invoke DefWindowProc,hWnd,uMsg,wParam,lParam
                ret
        .endif
        xor eax,eax
        ret
WndProc endp

End DllEntry

Analyse:

Dans un premier temps, nous allons nous pencher sur le code du programme principal.
 invoke LoadLibrary,addr Libname
 .if eax!=NULL
    invoke FreeLibrary,eax
 .endif
Nous appelons LoadLibrary pour charger la DLL nommée "splash.DLL". Et après ça, déchargez-la de la mémoire avec FreeLibrary. LoadLibrary ne retournera rien avant que la DLL n'en ait finie avec son initialisation..
C'est tout pour le programme principal. La partie intéressante c'est la DLL.

   .if reason==DLL_PROCESS_ATTACH  ; Quand la DLL est chargée.
      push hInst
      pop hInstance
      call ShowBitMap

Lorsque la DLL est chargée, Windows appelle son entrypoint avec le flag DLL_PROCESS_ATTACH. Nous en profitons pour afficher le Splash screen. En premier lieu, nous stockons l'instance handle de la DLL pour pouvoir l'utiliser plus tard. On appelle donc une fonction nommée ShowBitMap pour faire ce travail. ShowBitMap enregistre (une Window Class) une classe de fenêtre, et on crée la fenêtre puis on entre dans la boucle de message comme d'habitude. La partie intéressante est dans l'appel de CreateWindowEx :

        INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\
           WS_POPUP,CW_USEDEFAULT,\
           CW_USEDEFAULT,250,250,NULL,NULL,\
           hInstance,NULL

Notez que le style de fenêtre est seulement WS_POPUP ce qui fera qu'on obtiendra une fenêtre sans bords et sans titre. Nous limitons aussi la largeur et la hauteur de la fenêtre à 250x250 pixels.
Maintenant que la fenêtre est créée, avec le message WM_CREATE nous plaçons la fenêtre au centre de l'écran grâce au code suivant.

                invoke GetWindowRect,hWnd,addr DlgRect
                invoke GetDesktopWindow
                mov ecx,eax
                invoke GetWindowRect,ecx,addr DesktopRect
                push  0
                mov  eax,DlgRect.bottom
                sub  eax,DlgRect.top
                mov  DlgHeight,eax
                push eax
                mov  eax,DlgRect.right
                sub  eax,DlgRect.left
                mov  DlgWidth,eax
                push eax
                mov  eax,DesktopRect.bottom
                sub  eax,DlgHeight
                shr  eax,1
                push eax
                mov  eax,DesktopRect.right
                sub  eax,DlgWidth
                shr  eax,1
                push eax
                push hWnd
                call MoveWindow

Ceci retrouve les dimensions de notre bureau (qui vont servir de référence) puis celles de la fenêtre en calculant les coordonnées appropriées du coin supérieur gauche de la fenêtre pour pouvoir la placer au centre.

                invoke LoadBitmap,hInstance,addr BitmapName
                mov hBitMap,eax
                invoke SetTimer,hWnd,1,2000,NULL
                mov TimerID,eax

Ensuite on charge le 'bitmap ressource' avec LoadBitmap et on crée un minuteur (un Timer) avec l'ID du Timer ayant la valeur de '1', et un intervalle de temps de 2 secondes. Le Timer enverra des messages WM_TIMER à la fenêtre toutes les 2 secondes.

        .elseif uMsg==WM_PAINT
                invoke BeginPaint,hWnd,addr ps
                mov hdc,eax
                invoke CreateCompatibleDC,hdc
                mov hMemoryDC,eax
                invoke SelectObject,eax,hBitMap
                mov hOldBmp,eax
                invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap
                invoke StretchBlt,hdc,0,0,250,250,\
                       hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY
                invoke SelectObject,hMemoryDC,hOldBmp
                invoke DeleteDC,hMemoryDC
                invoke EndPaint,hWnd,addr ps

Quand la fenêtre reçoit le message WM_PAINT, elle crée une mémoire DC, choisit le bitmap dans la mémoire DC, obtient la taille du bitmap avec GetObject et met ensuite le bitmap sur la fenêtre en appelant StretchBlt, lequel se comporte comme BitBlt (il recopie le bitmap sur notre fenêtre) mais il est en plus capable de compresser ou de décompresser le bitmap à la dimension désirée. Dans notre cas, nous voulons que le bitmap s'accorde aux dimensions de la fenêtre, c'est pourquoi nous utilisons StretchBlt au lieu de BitBlt. Nous supprimons la mémoire DC après ça.

        .elseif uMsg==WM_LBUTTONDOWN
                invoke DestroyWindow,hWnd

Ce serait frustrant pour l'utilisateur qu'il soit obligé d'attendre que le Splash screen disparaisse. On aimerait que l'utilisateur ait le choix. Quand il clique sur Splash screen, il faudrait qu'il disparaisse. C'est pourquoi nous avons besoin de traiter le message WM_LBUTTONDOWN dans la DLL. En recevant ce message, la fenêtre est détruite par l'appel de DestroyWindow.

        .elseif uMsg==WM_TIMER
                invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL
                invoke KillTimer,hWnd,TimerID

Si l'utilisateur attend, le Splash screen disparaîtra lorsque le temps indiqué se sera écoulé (dans notre exemple, au bout de 2 secondes). On fait ça en traitant le message WM_TIMER. Après avoir reçu ce message, nous fermons la fenêtre en lui envoyant le message WM_LBUTTONDOWN. Ça permet d'éviter la duplication du code. Puisque nous n'avons plus besoin d'utiliser le Timer, nous le détruisons avec KillTimer.
Après que la fenêtre soit fermée, la DLL renverra le contrôle au programme principal.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32asm Tutorial 27: Tooltip Control

Tutorial 27: Les Bulles d'Information
(Tooltip Control)

Nous allons voir ce qu'est un tooltip et comment le créer puis l'employer. Downloadez l'exemple.

Théorie:

Un tooltip est une petite fenêtre rectangulaire qui apparaît lorsque le pointeur de la souris plane au dessus d'une zone spécifique. En se moment même vous êtes en train de lire ce tutorial, et bien regardez tout en bas à droite, il y a votre horloge. Déplacez votre souris dessus et ne la touchez plus ne cliquez sur rien… voilà ! un ToolTip vient d'apparaître ! c'est ça un ToolTip ! Chaque fenêtre tooltip contient le texte que le programmeur souhaite montrer. À cet égard, un serveur tooltip à le même rôle qu'une fenêtre de statut sauf que ce premier disparaît lorsque l'utilisateur clique ou déplace le pointeur de la souris en dehors du secteur désigné. Vous êtes probablement familiers avec les tooltips qui sont associés aux boutons de la barre d'outils. Ces "tooltips" sont utilement fourni par le contrôle de barre d'outils. Si vous souhaitez avoir des tooltips pour d'autres fenêtres ou commandes, vous avez besoin de créer votre propre contrôle tooltip.
Maintenant que vous savez ce qu'est un tooltip, on va continuer pour voir comment nous pouvons le créer puis l'employer. Les étapes sont déclinées ci-dessous :
  1. Créez un tooltip control avec CreateWindowEx.
  2. Définissez une région, qui si le pointeur de la souris passe dessus, déclenche l'affichage d'un tooltip.
  3. Soumettez cette région au tooltip control.
  4. Retransmettez les messages souris de la région soumise vers le tooltip control (cette étape peut arriver plus tôt, ça dépend de la méthode employée pour retransmettre les messages).
Nous allons ensuite examiner chaque étape en détail.

Création d'un Tooltip:

Un 'tooltip control' est un 'common control' (voir tutorial 18). En tant que tel, vous avez besoin d'appeler InitCommonControls à un moment dans votre code source pour que MASM lie implicitement votre programme avec comctl32.dll. Vous créez un tooltip control avec CreateWindowEx. Le scénario typique est le suivant :
.data
TooltipClassName db "Tooltips_class32",0
.code
.....
invoke InitCommonControls
invoke CreateWindowEx, NULL, addr TooltipClassName, NULL, TIS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL
Remarquez le 'window style': TIS_ALWAYSTIP. Ce style indique que l'on affichera le tooltip lorsque le pointeur de la souris est placée au-dessus du secteur désigné, indépendamment du statut de la fenêtre qui contient ce secteur. Mis seul, si vous employez ce Flag, quand le pointeur de la souris plane sur le secteur, vous faites référence au tooltip control, et la bulle tooltip apparaîtra même si la fenêtre (ou l'objet) sous le pointeur de la souris est inactive.
Vous n'avez pas besoin d'inclure les styles WS_POPUP ni WS_EX_TOOLWINDOW dans CreateWindowEx parce que la procédure de fenêtre du tooltip control les ajoute automatiquement. Vous n'avez pas besoin non plus d'indiquer les coordonnées, la hauteur ni la largeur de la fenêtre tooltip : le tooltip control les ajustera automatiquement en fonction de la longueur de votre texte tooltip qui devra être affichéé, donc nous mettons CW_USEDEFAULT dans les quatre paramètres. Les paramètres restants ne sont négligeables.

Propriétés et définition du Tool (de l'outil).

Le tooltip control est créé mais on ne le montre pas immédiatement. Nous souhaitons que la fenêtre tooltip s'affiche seulement quand le pointeur de la souris plane sur un lieu bien défini. Il est maintenant temps de définir ce secteur. Nous appelons un tel secteur "tool". Chaque tool est un secteur rectangulaire affiché au-dessus du secteur client d'une fenêtre que le tooltip control contrôlera avec le pointeur de la souris. Si la souris passe au dessus d'un tool, la fenêtre tooltip apparaîtra. Ce secteur rectangulaire peut recouvrir le secteur client entier ou bien seulement une partie. Donc nous pouvons divisé les tools dans deux catégories : un qui est fonctionne comme une fenêtre et l'autre qui est fonctionne comme un petit secteur rectangulaire dans le secteur client d'une fenêtre. Tous les deux ont leurs propres utilisations. Un Tool qui recouvre le secteur client d'une fenêtre en entier est le plus fréquemment utilisé, avec en prime d'autres commandes tels que des boutons, des commandes d'édition etc…. Vous n'avez pas besoin de définir les coordonnées ni les dimensions de ce genre de tool : il sera forcément de la taille du secteur client de la fenêtre. Pour un Tool qui fonctionne comme un petit secteur rectangulaire sur le secteur client, on l'utilise seulement lorsque on veut diviser le secteur client d'une fenêtre en plusieurs régions sans employer de fenêtres d'enfant (Child Windows). Avec ce type de tool, vous avez besoin de définir les coordonnées du coin supérieur gauche ainsi que sa largeur et sa hauteur.
Vous définissez un tool grâce à la structure TOOLINFO laquelle a le prototype de fonction suivant :
TOOLINFO STRUCT
  cbSize             DWORD      ?
  uFlags             DWORD      ?
  hWnd               DWORD      ?
  uId                DWORD      ?
  rect               RECT      <>
  hInst              DWORD      ?
  lpszText           DWORD      ?
  lParam             LPARAM     ?
TOOLINFO ENDS
Noms | Explications
cbSize | est la taille de la structure TOOLINFO. Vous DEVEZ remplir ce membre. Windows vous renverra un flag pour dire qu'il y a une erreur si ce champ n'est pas rempli correctement et vous obtiendrez des résultats étranges, imprévisibles.
uFlags est un flag (de la taille d'un bit) qui défini les caractéristiques du tool. Cette valeur peut être une combinaison de plusieurs flags que voici :
  • TTF_IDISHWND ou "ID est hWnd". Si vous mettez ce flag, ça signifie que vous souhaitez utiliser un tool qui recouvre le secteur client tout entier d'une fenêtre (le premier type de tool vu plus haut). Si vous employez ce flag, vous devez remplir le membre uID de cette structure avec l' handle de la fenêtre que vous souhaitez employer. Si vous ne mettez pas ce Flag, c'est que vous préférez utiliser le deuxième type de tool, celui qui fonctionne comme un petit secteur rectangulaire sur votre fenêtre client. Dans ce cas, vous avez besoin de remplir le membre rect avec les dimensions du rectangle.
  • TTF_CENTERTIP Normalement la fenêtre tooltip apparaîtra à droite et au-dessous du pointeur de la souris. Si vous mettez ce Flag, la fenêtre tooltip apparaîtra toujours directement au-dessous du tool et sera placé indépendamment de la position de la souris.
  • TTF_RTLREADING Vous pouvez laisser tomber ce Flag si votre programme n'est pas conçu spécialement pour des systèmes arabes ou hébreux. Ce Flag affiche le texte tooltip en laissant la possibilité du sens de lecture. Ça ne fonctionnera pas avec d'autres systèmes (comme le notre, système Cyrillique).
  • TTF_SUBCLASS Si vous utilisez ce Flag, ça signifie que vous indiquez au tooltip control de sous-classer la fenêtre sur laquelle le tool est branché, ainsi le tooltip control pourra intercepter les messages souris qui sont envoyés à cette fenêtre. Ce Flag est très pratique. Si vous ne l'employez pas, vous devez faire plus de boulot pour relayer les messages souris vers le tooltip control.
hWnd est l'handle de la fenêtre qui contient le tool. Si vous mettez le flag TTF_IDISHWND, ce champ sera ignoré puisque Windows utilisera la valeur du membre uId en tant qu' handle de la fenêtre. Vous avez besoin de remplir ce champ si :
  • Vous n'utilisez pas le Flag TTF_IDISHWND (Autrement dit, vous utilisez un petit tool rectangulaire)
  • Vous mettez la valeur LPSTR_TEXTCALLBACK dans le membre lpszText. Cette valeur informe le tooltip control que, lorsqu'on a besoin d'afficher la fenêtre tooltip, on doit solliciter cette fenêtre pour que le texte soit affiché. C'est une sorte de mise à jour dynamique du texte tooltip en temps réel. Si vous souhaitez changer dynamiquement votre texte tooltip, vous devez mettre la valeur LPSTR_TEXTCALLBACK dans le membre lpszText. Le tooltip control enverra le message d'avis (l'information) TTN_NEEDTEXT dans la fenêtre identifiée par l'handle hWnd.
uId La valeur a mettre dans ce champ peut avoir deux significations. ça dépend si le membre uFlags (vu plus haut) contient le Flag TTF_IDISHWND.
  • C'est une application définissant l'ID du tool si le flagTTF_IDISHWND n'a pas été indiqué. Puisque dans ce cas vous employez un tool qui recouvre uniquement une petite partie du secteur client, c'est logique que vous puissiez avoir beaucoup de ces mêmes outils semblables sur votre secteur client (sans qu'ils se chevauchent). Le tooltip control a besoin de pouvoir les différencier. Dans ce cas, l'handle de la fenêtre qui est dans le membre hWnd n'est pas assez sélectif puisque tous ces outils sont sur cette même fenêtre. Les 'IDs' sont donc nécessaires. L'IDs représenter n'importe quelles valeurs tant qu'elle restent uniques entre elles.
  • C'est l'handle de la fenêtre dont on utilise la totalité de son secteur client pour afficher le tool si le Flag TTF_IDISHWND a été spécifié. Vous pouvez vous demander pourquoi ce champ est utilisé pour stocker l'handle de la fenêtre au lieu de hWnd. La réponse c'est que le membre hWnd peut déjà être rempli si la valeur LPSTR_TEXTCALLBACK a déjà été spécifiée dans le membre lpszText , en plus la fenêtre qui est responsable de l'approvisionnement du texte tooltip ainsi que la fenêtre qui contient le tool ne peuvent pas être les mêmes (Vous pouvez construire votre programme pour qu'une simple fenêtre puisse jouer ces deux rôles mais c'est trop restrictif. Dans ce cas, Microsoft vous donne plus de liberté. Acclamations.)
rect | La structure RECT définie les dimensions du tool. Cette structure définit un rectangle positionné par rapport au coin supérieur gauche du secteur client de la fenêtre (qui peut aussi être un simple objet) choisi par le membre hWnd. Bref, vous devez remplir cette structure si vous voulez avoir un tool qui ne recouvre qu'une partie du secteur client. Le tooltip control ignorera ce champ si vous avez déjà mis le Flag TTF_IDISHWND (Car dans ce cas, c'est que souhaitiez utiliser un tool qui recouvre la totalité de secteur client. Enfin Normalement ! ! ! )
hInst | est l'handle de l'instance (du groupe de données) qui contient la chaîne de caractères en ressource lequel sera employé en tant que 'tooltip texte' uniquement si la valeur dans le membre lpszText pointe sur cette chaîne de caractères. Ça peut paraître confus. Lisez d'abord l'explication sur le membre lpszText et vous comprendrez à quoi sert réellement ce champ. Le tooltip control ignora ce champ si le champ lpszText ne contient pas l'identificateur de la chaîne de caractères en ressource.
lpszText Ce champ peut avoir plusieurs valeurs :
  • Si vous mettez la valeur LPSTR_TEXTCALLBACK dans ce champ, le tooltip control enverra le message d'avis TTN_NEEDTEXT à la fenêtre identifiée par son handle dans le champs hWnd pour que la chaîne de caractères soit affichée dans la fenêtre tooltip. C'est la méthode la plus dynamique de mise à jour du texte tooltip : vous pouvez changer ce tooltip texte à chaque fois que la fenêtre tooltip est affichée.
  • Si vous mettez l'identificateur de la chaîne de caractères en ressource dans ce champ, alors lorsque le tooltip control en aura besoin pour montrer le tooltip texte dans la fenêtre tooltip, il recherchera cette chaîne dans le tableau des chaînes de caractères de grâce au membre hInst. Le tooltip control identifie l'identificateur de la chaîne de caractères (en ressource) en vérifiant le mot de poids fort de ce champ (de ce paramètre). Puisque c'est une valeur sur 16 bits, le mot de poids fort dans ce paramètre sera toujours initialisé à zéro. C'est une méthode utile si vous destinez adapter votre programme à d'autres langues. Puisque la chaîne de caractères est définie dans un scénario de ressource, vous n'avez pas besoin de modifier le code source de votre programme lui-même. Il vous suffit seulement de modifier la table des chaînes de caractères et ainsi les tooltip textes changeront sans qu'il y ait le risque que des bogues viennent foutre le bordel dans votre programme.
  • Si la valeur dans ce paramètre n'est pas LPSTR_TEXTCALLBACK et que le mot de poids fort n'est pas à zéro, le tooltip control interprète cette valeur comme l'indicateur (le pointeur) qui est sur la chaîne de caractères qui servira de tooltip texte. Cette méthode est la plus facile à utiliser, mais elle reste moindre flexible.

Pour récapituler, vous avez besoin de remplir la structure TOOLINFO avant de la soumettre au tooltip control. Cette structure définit les caractéristiques du tool que vous désirez obtenir.

Prendre en compte l'existence du 'tool' avec le 'tooltip control'

Après que vous ayez rempli la structure TOOLINFO, vous devez la soumettre au tooltip control. Le tooltip control peut gérer en même temps plusieurs Tools donc il est en général inutile de créer plusieurs tooltip control pour une même fenêtre. Pour associer un tool à un tooltip control, on envoie le message TTM_ADDTOOL au tooltip control. wParam n'est pas utilisé, mais lParam doit absolument contenir l'adresse de la structure TOOLINFO que vous souhaitez enregistrer (lui associer).
.data?
ti TOOLINFO <>
.......
.code
.......
<fill the TOOLINFO structure>
.......
invoke SendMessage, hwndTooltip, TTM_ADDTOOL, NULL, addr ti
SendMessage: Ce message renverra la valeur TRUE si le tool est enregistré avec succès au près du tooltip control, FALSE dans le cas contraire.
Vous pouvez 'désenregistrer' un tool en envoyant le message TTM_DELTOOL au tooltip control.

Transmission des Messages Souris jusqu'au Tooltip Control

Quand on a achevé l'étape ci-dessus, le tooltip control sait quels secteurs (zones) est-ce qu'il doit contrôler quand la souris passe dessus et aussi quel texte il doit afficher dans la fenêtre tooltip. La seule chose qui manque à cette action c'est l' *élément qui la déclenche*. Pensez-y : la zone indiqué par le tool est sur le secteur client de l'autre fenêtre. Comment un tooltip control peut-il intercepter les messages souris destinés à cette fenêtre ? On a besoin de chronométrer les temps de passage du pointeur de la souris au dessus d'un tool pour que si ce temps écoulé est suffisant, pour que le tooltip control affiche la fenêtre du tooltip. Il y a deux méthodes pour faire ça, la première exige la coopération de la fenêtre qui contient le tool alors que l'autre s'effectue sans coopération de la part de cette fenêtre.
  • La fenêtre qui contient le tool doit transmettre les messages souris jusqu'au tooltip control en les lui envoyant avec le message TTM_RELAYEVENT. La partie lParam de ce message doit contenir l'adresse de la structure MSG laquelle signifie que le message doit être transmis au tooltip control. Le tooltip control traite seulement les messages souris suivants :
    • WM_LBUTTONDOWN
    • WM_MOUSEMOVE
    • WM_LBUTTONUP
    • WM_RBUTTONDOWN
    • WM_MBUTTONDOWN
    • WM_RBUTTONUP
    • WM_MBUTTONUP
    Tous les autres messages seront ignorés. Ainsi dans la 'procédure de fenêtre' de la fenêtre qui contient le tool, il doit y avoir un commutateur un peu dans le genre :

    WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .......
    if uMsg==WM_CREATE
    .............
    elseif uMsg==WM_LBUTTONDOWN || uMsg==WM_MOUSEMOVE || uMsg==WM_LBUTTONUP || uMsg==WM_RBUTTONDOWN || uMsg==WM_MBUTTONDOWN || uMsg==WM_RBUTTONUP || uMsg==WM_MBUTTONUP
    invoke SendMessage, hwndTooltip, TTM_RELAYEVENT, NULL, addr msg
    ..........

  • Vous pouvez mettre le Flag TTF_SUBCLASS dans le membre uFlags de la structure TOOLINFO. Ce Flag dit au tooltip control de sous-classer la fenêtre qui contient le tool ainsi on peut intercepter les messages souris sans aucune coopération de la fenêtre. Cette méthode est plus facile à utiliser puisque elle n'exige pas de code supplémentaire grâce au Flag TTF_SUBCLASS et en plus le tooltip control s'occupe de toutes les interceptions de message par lui-même.
Voilà. À ce niveau, votre tooltip control est entièrement fonctionnel. Mais il y a plusieurs messages utiles, liés au tooltip, que vous devez connaître.
  • TTM_ACTIVATE. Sert à Activer/Désactiver le fonctionnement dynamyge du tooltip control , ce message est pour vous. Si la valeur de wParam est TRUE,alors le tooltip control est activé. Si la valeur de wParam est FALSE, le tooltip control est mise hors de service. On active notre tooltip control avant même qu'il n'ait été créé ainsi vous n'avez pas besoin d'envoyer aucun message pour l'activer.
  • TTM_GETTOOLINFO et TTM_SETTOOLINFO. l'utilisation ces messages sert à obtenir/changer des valeurs dans la structure TOOLINFO après qu'il ait été soumis au tooltip control. Vous avez besoin d'indiquer le tool que vous avez besoin de changer avec les valeurs correctes de uID et hWnd. Si vous souhaitez seulement changer le membre rect, utilisez le message TTM_NEWTOOLRECT. Si vous voulez changer le 'tooltip texte', utilisez TTM_UPDATETIPTEXT.
  • TTM_SETDELAYTIME. Grâce à ce message, vous pouvez définir le temps d'attente avant que le tooltip control n'affiche le tooltip texte.

Exemple:

L'exemple suivant repésente une simple boîte de dialogue munie de deux boutons. Son secteur client est divisé en 4 secteurs : un secteur supérieur gauche, supérieur droit, inférieur droit et inférieur gauche. Chaque secteur sera définit en tant que tool avec son propre tooltip texte. Les deux boutons auront eux aussi leurs propres tooltip textes.
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
EnumChild proto :DWORD,:DWORD
SetDlgToolArea proto :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
.const
IDD_MAINDIALOG equ 101
.data
ToolTipsClassName db "Tooltips_class32",0
MainDialogText1 db "This is the upper left area of the dialog",0
MainDialogText2 db "This is the upper right area of the dialog",0
MainDialogText3 db "This is the lower left area of the dialog",0
MainDialogText4 db "This is the lower right area of the dialog",0
.data?
hwndTool dd ?
hInstance dd ?
.code
start:
    invoke GetModuleHandle,NULL
    mov hInstance,eax
    invoke DialogBoxParam,hInstance,IDD_MAINDIALOG,NULL,addr DlgProc,NULL
    invoke ExitProcess,eax

DlgProc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
    LOCAL ti:TOOLINFO
    LOCAL id:DWORD
    LOCAL rect:RECT
    .if uMsg==WM_INITDIALOG
        invoke InitCommonControls
        invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\
            TTS_ALWAYSTIP,CW_USEDEFAULT,\
            CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
            hInstance,NULL
        mov hwndTool,eax
        mov id,0
        mov ti.cbSize,sizeof TOOLINFO
        mov ti.uFlags,TTF_SUBCLASS
        push hDlg
        pop ti.hWnd
        invoke GetWindowRect,hDlg,addr rect
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect
        inc id
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText2,id,addr rect
        inc id
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText3,id,addr rect
        inc id
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText4,id,addr rect
        invoke EnumChildWindows,hDlg,addr EnumChild,addr ti
    .elseif uMsg==WM_CLOSE
        invoke EndDialog,hDlg,NULL
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
DlgProc endp

EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD
    LOCAL buffer[256]:BYTE
    mov edi,lParam
    assume edi:ptr TOOLINFO
    push hwndChild
    pop [edi].uId
    or [edi].uFlags,TTF_IDISHWND
    invoke GetWindowText,hwndChild,addr buffer,255
    lea eax,buffer
    mov [edi].lpszText,eax
    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi
    assume edi:nothing
    ret
EnumChild endp

SetDlgToolArea proc uses edi esi hDlg:DWORD,lpti:DWORD,lpText:DWORD,id:DWORD,lprect:DWORD
    mov edi,lpti
    mov esi,lprect
    assume esi:ptr RECT
    assume edi:ptr TOOLINFO
    .if id==0
        mov [edi].rect.left,0
        mov [edi].rect.top,0
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        shr eax,1
        mov [edi].rect.bottom,eax
    .elseif id==1
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        inc eax
        mov [edi].rect.left,eax
        mov [edi].rect.top,0
        mov eax,[esi].right
        sub eax,[esi].left
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        mov [edi].rect.bottom,eax
    .elseif id==2
        mov [edi].rect.left,0
        mov eax,[esi].bottom
        sub eax,[esi].top
        shr eax,1
        inc eax
        mov [edi].rect.top,eax
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        mov [edi].rect.bottom,eax
    .else
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        inc eax
        mov [edi].rect.left,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        shr eax,1
        inc eax
        mov [edi].rect.top,eax
        mov eax,[esi].right
        sub eax,[esi].left
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        mov [edi].rect.bottom,eax
    .endif
    push lpText
    pop [edi].lpszText
    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti
    assume edi:nothing
    assume esi:nothing
    ret
SetDlgToolArea endp
end start

Analyse:

Après que la fenêtre de dialogue principale ait été créée, nous créons le tooltip control à son tour avec CreateWindowEx.
invoke InitCommonControls
invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\
       TTS_ALWAYSTIP,CW_USEDEFAULT,\
       CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
       hInstance,NULL
mov hwndTool,eax
Après ça, on définit quatre Tools pour chaque coin de la boîte de dialogue.

    mov id,0        ; Employé en tant qu' ID du tool.
    mov ti.cbSize,sizeof TOOLINFO
    mov ti.uFlags,TTF_SUBCLASS    ; Impose au tooltip control de sous-classer la fenêtre de dialogue.
    push hDlg
    pop ti.hWnd    ; handle de la fenêtre qui contient le tool.
    invoke GetWindowRect,hDlg,addr rect    ; Obtient les dimensions du secteur client.
    invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect

Nous initialisons les membres de la structure TOOLINFO. Remarquez que nous voulons diviser le secteur client en 4 Tools donc nous avons besoin de connaitre les dimensions du secteur client. C'est pourquoi nous appelons GetWindowRect. Nous ne souhaitons pas transmettre les messages souris au tooltip control nous-mêmes donc nous mettons le Flag TIF_SUBCLASS.
SetDlgToolArea est une fonction qui construit le rectangle de limitation de chaque tool et enregistre ce tool vis à vis du tooltip control. Je n'entrerai pas dans les détails complexes du calcul, il suffirai pour dire qu'elle divise le secteur client en 4 secteurs de mêmes tailles. Donc on envoie le message TTM_ADDTOOL au tooltip control, en passant l'adresse de la structure TOOLINFO dans le paramètre lParam.

    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti

Après que ces 4 tools soient enregistrés, nous pouvons nous intéresser aux boutons de la boîte de dialogue. Nous pouvons mamipuler chaque bouton par son ID mais ce n'est pas pratique. A la place, nous emploierons l'appel API EnumChildWindows pour dénombrer tous les 'Controls' sur la boîte de dialogue et nous les enregistrons ensuite au près de notre tooltip control. EnumChildWindows a la syntaxe suivante :

EnumChildWindows proto hWnd:DWORD, lpEnumFunc:DWORD, lParam:DWORD
hWnd est l'handle de la fenêtre parente. LpEnumFunc est l'adresse de la fonction EnumChildProc laquelle sera appelée pour chaque contrôle ayant été dénombré. lParam est la valeur que l'on passera à la fonction EnumChildProc. La fonction EnumChildProc a la définition suivante :
EnumChildProc proto hwndChild:DWORD, lParam:DWORD
hwndChild est l'handle d'un contrôle ayant été dénombré par EnumChildWindows. lParam est la même valeur lParam que celle que nous avons passé à EnumChildWindows.
Dans notre exemple, nous appelons EnumChildWindows comme ça :
invoke EnumChildWindows,hDlg,addr EnumChild,addr ti
Nous passons l'adresse de la structure TOOLINFO dans le paramètre lParam parce que nous enregistrerons chaque Child Control de tooltip control dans la fonction EnumChild. Si nous n'utilisons pas cette méthode, nous devons déclarer ti en tant que variable globale, ce qui peut faire apparaître des bogues(défauts).
Lorsqu'on appelle EnumChildWindows, Windows dénombre les Childs Controls de notre boîte de dialogue et appelle la fonction EnumChild à chaque fois qu'il trouve un nouveau control. Donc si notre boîte de dialogue possède deux Childs Controls (ici 2 boutons), EnumChild sera appelée deux fois.
La fonction EnumChild remplit les membres appropriés de la structure TOOLINFO et enregistre ensuite le tool au près du tooltip control.
EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD
    LOCAL buffer[256]:BYTE
    mov edi,lParam
    assume edi:ptr TOOLINFO
    push hwndChild
    pop [edi].uId    ; Nous utilisons la totalité du secteur client du 'Control' en tant que 'tool'.
    or [edi].uFlags,TTF_IDISHWND
    invoke GetWindowText,hwndChild,addr buffer,255
    lea eax,buffer    ; Ceci utilise le texte de la fenêtre comme tooltip texte.
    mov [edi].lpszText,eax
    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi
    assume edi:nothing
    ret
EnumChild endp
Notez que dans ce cas, nous employons un type de tool différent: celui-ci recouvre le secteur client de la fenêtre en entier. Nous avons donc besoin de remplir l'élément uID avec l'handle de la fenêtre qui contient ce tool. C'est pourquoi nous devons mettre le Flag TTF_IDISHWND dans le membre uFlags.
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 28: Win32 Debug API Part 1

Tutorial 28: Win32 Debug API Partie 1/3

Dans ce tutorial, vous allons voir ce que Win32 offre comme mises au point primaires pour les développeurs. Lorsque vous en aurez terminés avec ce tutorial, vous saurez comment debugger un programme.
Downloadez l'exemple.

Théorie:

Win32 possède plusieurs APIs qui permettent aux programmeurs d'utiliser certaines des fonctions d'un débugger. On les appelle, les 'Win32 Debug APIs' ou 'primitives'. Avec elles, vous pouvez :

  • Charger un programme ou s'attacher à un programme en cours d'exécution pour le debugger.
  • Obtenir une information de bas niveau dans le programme que vous êtes en train de debugger, comme l'ID du process (du programme), l'adresse de son entrypoint, 'son image base' etc... (Par exemple quand vous cracker un logiciel avec W32Dasm, tous les programmes que vous désassemblez commencent à l'adresse 00400000. Ça c'est l'Image Base).
  • Prendre conscience des liens de parentés pour les phases de debugging qui ne vont pas l'un sans l'autre tels que pour un programme multi-liens 'Process/Liens' 'Start/Exit', les DLLs sont Chargés/déchargés en mémoire etc.
  • Modifier le process/lien qu'on est en train de debugger.

Bref, vous pouvez fabriquer un programme qui sert à debugger, grâce à ces APIs là (les Win32 Debug APIs). Puisque ce sujet est vaste, je le divise en plusieurs parties : ce tutorial étant la première partie. Dans celle-ci, j'expliquerai les concepts de base ainsi que la structure générale de l'utilisation des 'Win32 Debug APIs'.
Les étapes pour l'utilisation des 'Win32 Debug APIs' sont :

  1. Créez un process (dans W32Dasm c'est: Load Process) (Dans SoftIce c'est son: Loader) ou attachez votre programme à un process en cours de fonctionnement (Dans W32Dasm c'est: Attach to an Active Process) (Dans SoftIce c'est: CTRL+d). C'est la première étape de l'utilisation des 'Win32 debug APIs'. Puisque votre programme agira comme un DEBUGGER, vous aurez besoin d'un programme sur lequel faire vos essais. Un programme étant en train de se faire débugger (la cible) est appelé un debuggee (non je ne me suis pas trompé). Vous pouvez lancer une 'action debuggee' (action qui fait en sorte qu'on debug la cible, le debuggee) de deux façons :
    • Vous pouvez créer un 'Debuggee Process' par vous-même avec CreateProcess. Pour créer un process pour debugger, vous devez mettre le Flag DEBUG_PROCESS. Ce Flag précise à Windows que nous souhaitons debugger le process (de la même façon que Load Process pour W32Dasm). Windows enverra des notifications sur les événements importants servant à debugger (on va les appeler les 'debugging événements') qui arrivent dans votre programme debuggee. Votre debuggee sera immédiatement suspendu jusqu'à ce que votre programme ne soit prêt. Si le debuggee crée aussi des Child Process, Windows lui aussi enverra les 'debugging événements' dans tous les Child Process de votre programme. C'est un comportement qui d'habitude est indésirable. C'est pourquoi on peut le mettre hors service en spécifiant le Flag DEBUG_ONLY_THIS_PROCESS dans la combinaison de Flags DEBUG_PROCESS.
    • Vous pouvez 'attacher ' votre debugger à un programme cible qui est déjà en train de tourner, grâce à DebugActiveProcess.
  2. Attendez les 'debugging événements'. Après que votre programme n'ait acquis sa fonction de debugger, le lien primaire du debuggee est suspendu et continuera de l'être jusqu'à ce que vous appeliez WaitForDebugEvent dans votre programme. Cette fonction marche comme d'autres WaitForXXX fonctionnent, c'est-à-dire. Elle bloque le lien appelant tant que l'événement "attendu" ne se produise. Dans ce cas, elle attend que des 'debugging événements' soient envoyés par Windows. On va voir leur définition :

    WaitForDebugEvent Proto lpDebugEvent:DWORD, dwMilliseconds:DWORD

    lpDebugEvent est l'adresse de la structure DEBUG_EVENT, laquelle sera remplie de l'information d'un 'debugging événement' quand un événement particulier est en train de se passer dans le debuggee.

    dwMilliseconds est le temps en millisecondes que cette fonction attendra qu'un 'debugging événement' ne se produise. Si ce temps est écoulé et qu'aucun événement (servant à debugger) n'est arrivé, WaitForDebugEvent revient au Call. D'autre part, si vous mettez la constante INFINITE dans cet argument, la fonction ne retournera pas avant qu'un 'debugging événement' ne soit produit.

    Maintenant on va examiner la structure DEBUG_EVENT plus en détail.

    DEBUG_EVENT STRUCT
    dwDebugEventCode dd ?
    dwProcessId dd ?
    dwThreadId dd ?
    u DEBUGSTRUCT <>
    DEBUG_EVENT ENDS

    dwDebugEventCode contient la valeur (l'information) qui indique quel type de 'debugging événement' s'est produit dans la cible. Bref, il peut y avoir plusieurs types d'événements, votre programme a besoin de vérifier la valeur de cet élément pour qu'il sache de quel genre il est, et lui répondre convenablement. Les valeurs possibles sont les suivantes:

    3. Valeurs | Significations
CREATE_PROCESS_DEBUG_EVENT | Un process est créé. Cet événement sera envoyé lorsque le process du debuggee vient juste d'être créé (et n'est pas encore en fonction) ou lorsque votre programme s'attache juste à un process (un programme) en cours avec DebugActiveProcess. C'est le tout premier événement que votre programme recevra.
EXIT_PROCESS_DEBUG_EVENT | Un process vient de se terminer.
CREATE_THEAD_DEBUG_EVENT | Un nouveau lien est créé dans le process du debuggee ou quand votre programme s'attache d'abord à un process en cours. Notez que vous ne recevrez pas cet avis lorsque le lien primaire du debuggee est déjà créé.
EXIT_THREAD_DEBUG_EVENT | Un lien dans le debuggee vient de se terminer. Votre programme ne recevra pas cet événement pour le lien primaire. Bref, vous pouvez penser que le lien primaire du debuggee est un équivalent du process du debuggee lui-même. Ainsi, quand votre programme voit CREATE_PROCESS_DEBUG_EVENT, ça revient à CREATE_THREAD_DEBUG_EVENT pour le lien primaire.
LOAD_DLL_DEBUG_EVENT | Le debuggee charge une DLL. Vous recevrez cet événement quand le PE Loader résout les premiers liens de la DLL (vous appelez CreateProcess pour charger le debuggee), et au moment où le debuggee appelle LoadLibrary.
UNLOAD_DLL_DEBUG_EVENT | Une DLL est déchargé du process du debuggee.
EXCEPTION_DEBUG_EVENT | Une exception vient d'arriver dans le process du debuggee. Important: Cet événement arrivera uniquement avant que le debuggee ne commence à exécuter sa première instruction. L'exception est en réalité une pause debugging (int 3h) = (c'est un Break Point). Quand vous voulez reprendre le cours du debuggee, appelez ContinueDebugEvent avec le flag DBG_CONTINUE. N'utilisez pas le flag DBG_EXCEPTION_NOT_HANDLED sinon le debuggee refusera de repartir sous NT (sur Win98, ça marche très bien).
OUTPUT_DEBUG_STRING_EVENT | Cet événement est produit quand le debuggee appelle la fonction DebugOutputString pour envoyer une chaîne de caractères en tant que message à votre programme.
RIP_EVENT | Une erreur de debugging s'est produite.

    dwProcessId et dwThreadId sont les IDs du process et du lien, du programme dans lequel un 'debugging événement' s'est produit. Vous pouvez employer ces valeurs en tant qu'identificateurs du process et du lien auquel vous vous intéressez. Rappelez-vous que si vous employez CreateProcess pour charger le debuggee, vous obtenez aussi les IDs du process et du lien du debuggee dans la structure PROCESS_INFO. Vous pouvez utiliser ces valeurs pour différencier les événements qui se produisent dans le debuggee lui-même de ceux qui se produisent dans ses Child Process (au cas où vous n'auriez pas spécifié le Flag DEBUG_ONLY_THIS_PROCESS).

    u est une union qui contient plus d'informations à propos du 'debugging événement'. ça peut être une des structures suivantes selon la valeur de dwDebugEventCode vu plus haut.

    Valeur dans dwDebugEventCode | Interprétation de 'u'
CREATE_PROCESS_DEBUG_EVENT | La structure CREATE_PROCESS_DEBUG_INFO nommée CreateProcessInfo
EXIT_PROCESS_DEBUG_EVENT | La structure EXIT_PROCESS_DEBUG_INFO nommée ExitProcess
CREATE_THREAD_DEBUG_EVENT | La structure CREATE_THREAD_DEBUG_INFO nommée CreateThread
EXIT_THREAD_DEBUG_EVENT | La structure EXIT_THREAD_DEBUG_EVENT nommée ExitThread
LOAD_DLL_DEBUG_EVENT | La structure LOAD_DLL_DEBUG_INFO nommée LoadDll
UNLOAD_DLL_DEBUG_EVENT | La structure UNLOAD_DLL_DEBUG_INFO nommée UnloadDll
EXCEPTION_DEBUG_EVENT | La structure EXCEPTION_DEBUG_INFO nommée Exception
OUTPUT_DEBUG_STRING_EVENT | La structure OUTPUT_DEBUG_STRING_INFO nommée DebugString
RIP_EVENT | La structure RIP_INFO nommée RipInfo

    Je n'entrerai pas dans les détails de toutes ces structures dans ce tutorial, on va seulement s'attarder un peu sur la structure CREATE_PROCESS_DEBUG_INFO.
    Pour s'assurer que notre programme appelle WaitForDebugEvent et qu'il continue son déroulement ensuite, la première chose à faire est de retrouver quel 'debugging événement' s'est produit dans la cible'. Cette événement a été placé dans le membre dwDebugEventCode donc il suffit d'examiner la valeur dans dwDebugEventCode pour voir quel type de 'debugging événement' s'est produit dans le process du debuggee. Par exemple, si la valeur dans dwDebugEventCode est CREATE_PROCESS_DEBUG_EVENT, on peut interpréter le membre dans u comme étant CreateProcessInfo et y accéder grâce à u.CreateProcessInfo .

  3. Faites ce que vous voulez des réponses d'un 'debugging événement'. Lorsqu'on a un retour de WaitForDebugEvent, ça signifie qu'un 'debugging événement' vient juste de se dérouler dans le process du debuggee ou bien c'est que le temps d'attente s'est écoulé sans que rien ne se soit passé. Votre programme a besoin d'examiner la valeur dans dwDebugEventCode pour réagir convenablement à cet événement. À cet égard, ça se passe comme pour le traitement des 'Window Messages' (ex: on vient de détecter le déplacement de la souris, frappe clavier...) : vous choisissez d'en traiter certains mais on en ignore d'autres.
  4. Laissez le debuggee continuer son déroulement. Quand un 'debugging événement' se produit, Windows suspend le debuggee. Mais dès que vous avez terminé de traiter cet événement, vous avez besoin de le relancer (le debuggee) de nouveau, là où il s'était arrêté. On fait ça en appelant la fonction ContinueDebugEvent.

    ContinueDebugEvent proto dwProcessId:DWORD, dwThreadId:DWORD, dwContinueStatus:DWORD

    Cette fonction reprend le lien qui a été précédemment suspendu à cause du fait qu'un 'debugging événement' soit arrivé.
    dwProcessId et dwThreadId sont les 'IDs du process et du lien', du programme cible qui va enfin être relancé. On obtient d'habitude ces deux valeurs des membres dwProcessId et dwThreadId de la structure DEBUG_EVENT.
    dwContinueStatus indique comment continuer le lien qui a annoncé le 'debugging événement'. Il y a deux valeurs possibles: DBG_CONTINUE et DBG_EXCEPTION_NOT_HANDLED. En ce qui concerne tous les autres 'debugging événements', ces deux valeurs font la même chose : reprendre le lien. L'exception est EXCEPTION_debug_EVENT. Si le lien annonce un 'événement exception' de debugging, ça signifie qu'une exception s'est produite dans un lien du debuggee. Si vous mettez DBG_CONTINUE , Le lien ignorera son propre traitement d'exception et continuera son exécution. Dans ce scénario, votre programme doit examiner et résoudre l'exception lui-même avant de pouvoir reprendre son lien avec DBG_CONTINUE sinon l'exception arrivera de nouveau à maintes reprises.... Si vous mettez DBG_EXCEPTION_NOT_HANDLED, Votre programme indique à Windows qu'il n'a pas récupéré l'handle de l'exception : Windows devra utiliser le manipulateur d'exception par défaut du debuggee, pour s'occuper de cette exception.
    Pour conclure, si le 'debugging événement' se réfère à une exception dans le process du debuggee, vous devez appeler ContinueDebugEvent avec le flag DBG_CONTINUE si votre programme a déjà éradiqué la cause de l'exception. Autrement, votre programme doit appeler ContinueDebugEvent avec le flag DBG_EXCEPTION_NOT_HANDLED. Sauf dans un cas, pour lequel vous devez toujours utiliser le flag DBG_CONTINUE: le premier EXCEPTION_debug_EVENT a la valeur EXCEPTION_BREAKPOINT dans le membre ExceptionCode. Quand le debuggee va exécuter sa toute première instruction, votre programme recevra un événement d'exception de debugging. C'est en réalité une pause debugging (int 3h) (un Break Point). Si vous répondez en appelant ContinueDebugEvent avec le flag DBG_EXCEPTION_NOT_HANDLED, Windows NT refusera de redémarrer le debuggee (parce que personne ne se soucie de ça). Vous devez toujours employer le flag DBG_CONTINUE dans ce cas, pour dire à Windows que vous voulez que le lien reprenne.

  5. On continue ce cycle indéfiniment tant que le debuggee ne tombe pas sur son propre Exit Process . Votre programme (le debugger) doit présenter une boucle infinie un peu comme une boucle de message. La boucle ressemble à ça :

    .while TRUE
    invoke WaitForDebugEvent, addr DebugEvent, INFINITE
    .break .if DebugEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
    ; S'occupe des événements de debugging.
    invoke ContinueDebugEvent, DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
    .endw

    Une fois le programme (à debugger) pris au piège : vous commencez à le debugger, vous ne pouvez plus vous en détacher (en sortir) tant qu'il ne s'est pas terminé (tant qu'on arrive pas à un des ses Exits).

On va récapituler les étapes à nouveau :

  1. On Créer un process ou on attache notre debugger à un process (un programme) en cours d'exécution..
  2. On attend que des 'debugging événements' ne se produisent.
  3. On fait ce qu'on veut (des réponses) des 'debugging événements'.
  4. Laissez le debuggee reprendre son exécution..
  5. On continue ce cycle (les points 2,3,4 vus précédemment) dans une boucle infinie tant qu'on ne tombe pas sur l'ExitProcess du debuggee.

Exemple:

Cet exemple debugge un programme win32 et affiche les informations importantes telles que son handle, son ID, son Image Base, etc...

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib
.data
AppName db "Win32 Debug Example no.1",0
ofn OPENFILENAME <>
FilterString db "Executable Files",0,"*.exe",0
             db "All Files",0,"*.*",0,0
ExitProc db "The debuggee exits",0
NewThread db "A new thread is created",0
EndThread db "A thread is destroyed",0
ProcessInfo db "File Handle: %lx ",0dh,0Ah
            db "Process Handle: %lx",0Dh,0Ah
            db "Thread Handle: %lx",0Dh,0Ah
            db "Image Base: %lx",0Dh,0Ah
            db "Start Address: %lx",0
.data?
buffer db 512 dup(?)
startinfo STARTUPINFO <>
pi PROCESS_INFORMATION <>
DBEvent DEBUG_EVENT <>
.code
start:
mov ofn.lStructSize,sizeof ofn
mov ofn.lpstrFilter, offset FilterString
mov ofn.lpstrFile, offset buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke GetStartupInfo,addr startinfo
invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi
.while TRUE
   invoke WaitForDebugEvent, addr DBEvent, INFINITE
   .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION
       .break
   .elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
       invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress
       invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION
   .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
       .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
          invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
         .continue
       .endif
   .elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .endif
   invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw
invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread
.endif
invoke ExitProcess, 0
end start

Analyse:

Le programme remplit la structure OPENFILENAME et appelle ensuite GetOpenFileName pour laisser l'utilisateur choisir un programme à debugger.

invoke GetStartupInfo,addr startinfo
invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi

Quand l'utilisateur en a choisi un, on appelle CreateProcess pour charger ce programme. on appelle GetStartupInfo pour remplir la structure STARTUPINFO avec ses valeurs par défaut. Remarquez que nous employons debug_PROCESS combiné avec le flag debug_ONLY_THIS_PROCESS pour ne debugger que ce programme, et ne pas toucher à ses Child Process.

.while TRUE
   invoke WaitForDebugEvent, addr DBEvent, INFINITE

Quand le debuggee est chargé, notre debugger entre dans sa boucle de debugging infinie, en appelant WaitForDebugEvent. WaitForDebugEvent ne retournera pas avant qu'un 'debugging événement' ne se soit produit dans le debuggee parce que nous avons mis INFINITE en tant que deuxième paramètre. Quand un 'debugging événement' se produit, WaitForDebugEvent retourne et 'DBEvent' (DB-Event) est rempli de l'information représentant le 'debugging événement'. (Par exemple: CREATE_PROCESS_DEBUG_EVENT, ou EXIT_THREAD_DEBUG_EVENT...)

   .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION
       .break

Nous vérifions d'abord la valeur dans dwDebugEventCode. Si c'est EXIT_PROCESS_DEBUG_EVENT, nous affichons une MessageBox disant "The debuggee exits" (le debugee s'est arrêté car il est tombé sur sa fonction de terminaison ExitProcess) et ensuite on sort de la boucle de debugging (du debugger).

   .elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
       invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress
       invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION

Si la valeur dans dwDebugEventCode est CREATE_PROCESS_DEBUG_EVENT, alors nous affichons plusieurs informations intéressantes à propos du debuggee dans une MessageBox. Nous obtenons ces informations de u.CreateProcessInfo. CreateProcessInfo est une structure de type CREATE_PROCESS_DEBUG_INFO. Vous pouvez obtenir plus de renseignements sur cette structure en regardant votre référence Win32 API.

   .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
       .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
          invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
         .continue
       .endif

Si la valeur dans dwDebugEventCode est EXCEPTION_DEBUG_EVENT, nous devons vérifier plus loin si le type d'exception est exact. C'est une longue ligne de références emboîtées mais vous pouvez obtenir le type d'exception grâce au membre ExceptionCode. Si la valeur dans ExceptionCode est EXCEPTION_BREAKPOINT et que c'est la première fois (ou bien que nous sommes certains que le debuggee ne s'est jamais servit de l'interruption 3 du Dos (int 03h)), alors nous pouvons dire sans risque que cette exception s'est produite lorsque le debuggee a voulut exécuter sa toute première instruction. Quand on en a fini avec le traitement de cette exception, nous devons appeler ContinueDebugEvent avec le Flag DBG_CONTINUE pour permettre au debuggee de reprendre là où il s'était arrêté. Après ça, nous retournons pour attendre le prochain 'debugging événement'.

   .elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .endif

Si la valeur dans dwDebugEventCode est CREATE_THREAD_DEBUG_EVENT ou bien EXIT_THREAD_DEBUG_EVENT, on affiche une MessageBox disant que le lien est créé ou bien détruit.

invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw

Excepté pour le cas EXCEPTION_debug_EVENT, vu au-dessus, nous appelons ContinueDebugEvent avec le flag DBG_EXCEPTION_NOT_HANDLED pour reprendre le cours du debuggee.

invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread

Quand le debuggee se referme, nous sommes déjà en dehors de la boucle de debugging (de notre debugger)et devons fermer à la fois le process du debuggee et les handles de ses liens. La simple fermeture de ses handles ne signifie pas que nous détruisons le rapport process/lien. Ça signifie seulement que nous ne souhaitons plus désormais employer ces handles pour faire la relation entre process et lien.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 29: Win32 Debug API part 2

Tutorial 29: Win32 Debug API Partie 2/3

Nous continuons sur le sujet , soit sur les : 'win32 debug API'. Dans ce tutorial, nous allons voir comment à l'aide de notre programme debugger nous pouvons modifier le process du debuggee.
Downloadez l'exemple.

Théorie:

Dans le tutorial précédent, nous avons vu comment charger le debuggee et gérer les 'debugging événements' qui se produisent dans son process. Pour être efficace, notre programme doit être capable de modifier le process de debuggee. Il existe plusieurs APIs pour faire ça.

  • ReadProcessMemory Cette fonction vous permet de lire la mémoire dans le process désigné. Son prototype de fonction est le suivant :

    ReadProcessMemory proto hProcess:DWORD, lpBaseAddress:DWORD, lpBuffer:DWORD, nSize:DWORD, lpNumberOfBytesRead:DWORD

    hProcess est l'handle du process dans lequel vous souhaitez effectuer votre lecture.
    lpBaseAddress est l'adresse dans le process cible où vous souhaitez commencer votre lecture. Par exemple, si vous voulez lire 4 octets du debuggee, lequel commence à l'offset 401000, la valeur dans ce paramètre doit être 401000.
    lpBuffer est l'adresse du buffer qui recevra les octets qu'on va lire dans le process.
    nSize est le nombre d'octets que vous souhaitez lire. (Dans l'exemple ci-dessus, c'est donc 4)
    lpNumberOfBytesRead est l'adresse de la variable (de taille dword) qui reçoit le numbre d'octets qu'on aura réellement lus. Si vous vous en foutez, vous pouvez employer un NULL.

  • WriteProcessMemory est un homologue de ReadProcessMemory. Ça vous permet d'écrire dans la mémoire du process cible. Ses paramètres sont exactement les mêmes que ceux de ReadProcessMemory.

    Les deux fonctions API suivantes ont besoin d'une petite explication quant à leur utilisation. Dans un OS multi-taches tel que Windows, on peut avoir plusieurs programmes tournant en même temps. Windows donne un temps d'activité restreint à chacun d'eux. Quand ce temps d'activité expire, Windows gèle le programme qui était en train de tourner et passe au programme suivant qui prend maintenant la priorité la plus haute. juste avant de commuter sur ce nouveau programme, Windows a sauvegardé les valeurs du premier dans les Registres de lien (du premier) pour qu'au moment où il est temps de reprendre le lien, Windows puisse rétablir l'ancien *environnement* de ce lien. Les valeurs des Registres sauvegardés, représentent un ensemble qu'on appelle un contexte.
    Revenons à notre sujet. Lorsqu'un 'debugging événement' se produit, Windows suspend le debuggee. Le contexte du debuggee est sauvegardé. Puisque le debuggee est suspendu, on est sûrs que les valeurs dans le contexte resteront inchangées. Nous pouvons obtenir les valeurs de ce contexte (donc les valeurs des registres) grâce à la fonction GetThreadContext et nous pouvons les changer avec SetThreadContext.
    Ces deux APIs sont extrêmement puissantes. Avec elles, on a à notre portée la puissance du VxD pour contrôler le debuggee cible : on peut changer les valeurs des registres sauvegardés, et juste avant que le debuggee ne reprenne son exécution, on récupère les anciennes valeurs du contexte de nouveau dans les Registres. Quelque soit les changements qu'on a fait subir au contexte avant de revenir au 'relancement' du debuggee, notre debugger perçoit et contrôle tout. Pensez-y : on peut même changer la valeur du Registre EIP et ainsi détourner le flux exécution n'importe où, où on le souhaite! C'est à dire, l'EIP représente à chaque instant la ligne du programme qui est en train d'être lue par l'ordinateur. De fil en aiguille on avance dans la lecture du programme ligne par ligne. Maintenant, si on est capable de changer la valeur du registre EIP, on peut sauté vraiment où on veut. Alors que normalement c'est l'ordinateur et personne d'autre qui contrôle l'EIP. C'est quelque chose qu'on serait bien incapable de faire en temps normal.

    GetThreadContext proto hThread:DWORD, lpContext:DWORD

    hThread est l'handle du lien que vous voulez obtenir dans le context
    lpContext est l'adresse de la structure CONTEXT qui sera remplie, lorsque cette fonction retourne avec succès.

    SetThreadContext a exactement les mêmes paramètres. On va voir à quoi ressemble une structure de CONTEXT :

  • CONTEXT STRUCT
  • ContextFlags dd ?

    ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_DEBUG_REGISTERS
  • ;-----------------------------------------------------------------------------------------------------------
    iDr0 dd ?
    iDr1 dd ?
    iDr2 dd ?
    iDr3 dd ?
    iDr6 dd ?
    iDr7 dd ?

  • ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_FLOATING_POINT
  • ;-----------------------------------------------------------------------------------------------------------
  • FloatSave FLOATING_SAVE_AREA <>

  • ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_SEGMENTS
  • ;-----------------------------------------------------------------------------------------------------------
  • regGs dd ?
    regFs dd ?
    regEs dd ?
    regDs dd ?

  • ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_INTEGER
  • ;-----------------------------------------------------------------------------------------------------------
  • regEdi dd ?
    regEsi dd ?
    regEbx dd ?
    regEdx dd ?
    regEcx dd ?
    regEax dd ?

  • ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_CONTROL
  • ;-----------------------------------------------------------------------------------------------------------
  • regEbp dd ?
    regEip dd ?
    regCs dd ?
    regFlag dd ?
    regEsp dd ?
    regSs dd ?

  • ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_EXTENDED_REGISTERS
  • ;-----------------------------------------------------------------------------------------------------------
  • ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?) CONTEXT ENDS

    Comme vous pouvez le voir, les membres de ces structures sont des imitations des Registres réels du processeur. Avant que vous ne puissiez employer cet artifice, vous devez indiquer quels groupes de Registres vous souhaitez lire/écrire dans le membre ContextFlags. Par exemple, si vous voulez lire/écrire tous les Registres, vous devez mettre le membre CONTEXT_FULL dans la structure ContextFlags. Si vous voulez seulement lire/écrire regEbp, regEip, regCs, regFlag, regEsp ou regSs, vous devez mettre le membre CONTEXT_CONTROL dans ContextFlags.

    Encore une chose que vous devez vous rappeler, en employant la structure CONTEXT: elle doit absolument fonctionner avec des modèles dword sinon vous obtiendriez des résultats étranges sous NT. Vous devez mettre "align dword" juste au-dessus de la ligne qui la déclare, de cette façon :

    align dword
    MyContext CONTEXT <>

Exemple:

Ce premier exemple sert à montrer comment on utilise un Debugger de type ActiveProcess. D'abord, on doit lancer une cible qui s'appelle win.exe, ce programme est une boucle infinie qui tourne en rond sans pouvoir en sortir et on ne peut donc pas arriver à son autre fonction (qui est juste après) qui affiche une fenêtre à l'écran. A partir de là, on lance notre exemple (notre debugger appelé debug2.exe), il s'attachera à win.exe et modifiera son code tel que win.exe puisse sortir de sa boucle infinie et ainsi il pourra afficher sa fenêtre.

Si ça ressemble pas à un Memory-Patch çà... :)

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib

.data
AppName db "Win32 Debug Example no.2",0
ClassName db "SimpleWinClass",0
SearchFail db "Cannot find the target process",0
TargetPatched db "Target patched!",0
buffer dw 9090h

.data?
DBEvent DEBUG_EVENT <>
ProcessId dd ?
ThreadId dd ?
align dword
context CONTEXT <>

.code
start:
invoke FindWindow, addr ClassName, NULL
.if eax!=NULL
    invoke GetWindowThreadProcessId, eax, addr ProcessId
    mov ThreadId, eax
    invoke DebugActiveProcess, ProcessId
    .while TRUE
       invoke WaitForDebugEvent, addr DBEvent, INFINITE
       .break .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
          mov context.ContextFlags, CONTEXT_CONTROL
          invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
          invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL
          invoke MessageBox, 0, addr TargetPatched, addr AppName, MB_OK+MB_ICONINFORMATION
       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
          .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
             invoke ContinueDebugEvent, DBEvent.dwProcessId,DBEvent.dwThreadId, DBG_CONTINUE
             .continue
          .endif
       .endif
       invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
   .endw
.else
    invoke MessageBox, 0, addr SearchFail, addr AppName,MB_OK+MB_ICONERROR
.endif
invoke ExitProcess, 0
end start

;--------------------------------------------------------------------
; Le code source de win.asm est en réalité une simple fenêtre comme
; celle de l'exemple du tutorial 2 avec une boucle infinie insérée
; juste avant qu'on ne puisse arriver jusqu'à elle (la fenêtre).
;----------------------------------------------------------------------

......
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL
mov hwnd,eax
jmp $ ;<---- Voici notre boucle infinie. Elle boucle sur elle-même.
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.while TRUE
   invoke GetMessage, ADDR msg,NULL,0,0
   .break .if (!eax)
   invoke TranslateMessage, ADDR msg
   invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

Analyse:

invoke FindWindow, addr ClassName, NULL

Notre programme a besoin de s'attacher au debuggee avec DebugActiveProcess lequel a besoin de l'ID du process du debuggee. Nous pouvons obtenir l'ID de son process en appelant GetWindowThreadProcessId qui a à son tour à besoin de l'handle de la fenêtre en tant que paramètre. Donc nous avons d'abord besoin d'obtenir cet handle.
Avec FindWindow, nous pouvons spécifier le nom de la Window Class (classe de fenêtre) dont nous avons besoin. Il renvoie l'handle de la fenêtre créée par cette Window Class. S'il renvoie un NULL, aucune fenêtre de cette classe n'est présente.

.if eax!=NULL
    invoke GetWindowThreadProcessId, eax, addr ProcessId
    mov ThreadId, eax
    invoke DebugActiveProcess, ProcessId

Après avoir obtenu l'ID du process, nous pouvons appeler DebugActiveProcess. Comme ça, nous passons à la boucle traitant les 'debugging événements'.

       .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
          mov context.ContextFlags, CONTEXT_CONTROL
          invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context

Quand on arrive à la partie du programme qui contient la ligne CREATE_PROCESS_debug_INFO, ça signifie que le debuggee est suspendu, disponible pour que nous puissions faire l'acte de chirurgie en son process. Dans cet exemple, nous remplaçons l'instruction de la boucle infinie dans le debuggee (qui est un Jmp codé en langage machine par 0EBh 0FEh) par deux NOP (90h 90h).
D'abord, nous avons besoin d'obtenir l'adresse où se situe cette instruction (ce Jmp). Puisque le debuggee est déjà dans la boucle au moment où notre programme est attaché à lui, alors EIP indique forcément l'adresse de cette instruction. Tout ce que nous avons besoin de faire, c'est de récupérer la valeur de EIP. On utilise GetThreadContext pour faire ça. Nous plaçons le membre ContextFlags dans le CONTEXT_CONTROL pour indiquer à GetThreadContext que nous souhaitons qu'il remplisse les Registres de "control" avec les membres de la structure CONTEXT. Ainsi par exemple EIP sera récupéré dans notre Registre de Control appelé regEip.

invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL

Maintenant que nous avons la valeur de l'EIP, nous pouvons appeler WriteProcessMemory pour récrire par dessus l'instruction "jmp $" avec 2 NOP, ainsi on aura aidé efficacement le debuggee à sortir de sa boucle infinie. Après ça, nous affichons un message destiné à l'utilisateur et appelons ensuite ContinueDebugEvent pour que le debuggee redémarre. Maintenant que l'instruction "jmp $" est remplacée par deux instructions NOP, le debuggee est capable de continuer jusqu'à atteindre l'affichage de sa fenêtre et du message. La preuve, c'est que nous verrons apparaître cette fenêtre à l'écran.

L'autre exemple (Debug3.exe) utilise une approche un peu différente pour casser la boucle infinie.

.......
.......
.if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
   mov context.ContextFlags, CONTEXT_CONTROL
   invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
   add context.regEip,2
   invoke SetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
   invoke MessageBox, 0, addr LoopSkipped, addr AppName, MB_OK+MB_ICONINFORMATION
.......
.......

Comme pour l'exemple précédent on appelle toujours GetThreadContext pour obtenir la valeur actuelle de l'EIP, mais au lieu de l'action : 'effacer la boucle du "jmp$" avec deux instructions NOP (pour briser la boucle), ici on incrémente la valeur de regEip par 2 (regEip-->EIP+2) grâce à l'instruction "skip over". Le résultat, c'est que lorsque le debuggee reprend le contrôle, il reprend son exécution à l'instruction suivante, juste après le "jmp$". Donc on ne détruit pas la boucle mais on passe par dessus. Vous allez me dire pourquoi +2 ! le saut 'jmp' est codé en langage machine sur deux octets (EB et FE ou 0EBh 0FEh) donc quand l'EIP est sur le 'jmp' on est sur EB si on ajoute 2 à l'EIP, on passe sur EB puis sur FE et donc on arrive à ce qui suit le jmp donc à l'instuction suivante. On vient de sauter par dessus !

Maintenant vous comprenez la puissance de 'Get & SetThreadContext'. Vous pouvez aussi modifier d'autres Registres et leurs valeurs seront ensuite rétablies après le passage du debuggee. Vous pouvez même insérer l'instruction 'int 3h' laquelle sert à placer des Break Points dans le programme qu'on est en train de debugger.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 30: Win32 Debug API part 3

Tutorial 30: Win32 Debug API partie 3/3

Dans ce tutorial, nous allons continuer l'exploration des 'win32 debug API'. Et spécialement, nous allons voir comment tracer le debuggee.
Downloadez l'exemple.

Théorie:

Si vous avez déjà utilisé un débugger auparavant, vous êtes familiers avec le fait de tracer un autre programme grâce à lui. Quand vous "tracez" un programme, les Break Points sur n'importe quelle instruction, vous donne la chance d'examiner les valeurs des Registres et des mémoires. Le fait de tracer un programme, on appelle ça officiellement 'Single-stepping'.
Plus concrètement, tracer un programme c'est exécuter une ligne d'instruction du programme cible puis voir ce que ça produit,( ou bien regarder les valeurs des Registres ou des mémoires qui viennent de changer) puis passer à la ligne suivante et encore voir ce qu'elle fait et ainsi de suite pour comprendre comment fonctionne le programme. Seul un debugger est capable de faire ça.
Le traçage pas à pas (ou 'Single-stepping') a la particularité d'être générer pas le CPU lui-même. (Le CPU, 'Central Processing Unit' ou 'Unité Centrale de Traitement'). Le 8ème bit du flag 'REGISTER' est appelé le Trap Flag. Si ce flag est mis à 1, le CPU fonctionne en mode pas à pas ou traçage, c'est pareil, enfin bon en 'Single-stepping'. Le CPU produira un 'debugging événement' de type 'exception' (voir tut28) pour le stopper après chaque instruction. Après que cette 'exception' se soit produite, le 'Trap Flag' est automatiquement purifié.
Nous pouvons aussi tracer le debuggee, en employant les win32 debug API. Les étapes sont les suivantes :

  1. Appelez la fonction GetThreadContext, en spécifiant le Flag CONTEXT_CONTROL dans le membre ContextFlags, pour obtenir la valeur du Flag REGISTER.
  2. Mettez le Trap Bit dans le membre regFlag de la structure CONTEXT.
  3. Appelez SetThreadContext
  4. Comme d'habitude, attendez qu'un 'debugging événement' se produise. Le debuggee s'exécutera en mode single-step. Après qu'il exécute chaque instruction, nous obtiendrons l'événement suivant : EXCEPTION_DEBUG_EVENT, et avec la valeur EXCEPTION_SINGLE_step dans u.Exception.pExceptionRecord.ExceptionCode nous pourrons tracer pas à pas.
  5. Si vous avez besoin de tracer (donc d'arrêter) l'instruction suivante, vous avez besoin de remettre le trap bit de nouveau.

Exemple:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib

.data
AppName db "Win32 Debug Example no.4",0
ofn OPENFILENAME <>
FilterString db "Executable Files",0,"*.exe",0
             db "All Files",0,"*.*",0,0
ExitProc db "The debuggee exits",0Dh,0Ah
         db "Total Instructions executed : %lu",0
TotalInstruction dd 0

.data?
buffer db 512 dup(?)
startinfo STARTUPINFO <>
pi PROCESS_INFORMATION <>
DBEvent DEBUG_EVENT <>
context CONTEXT <>

.code
start:
mov ofn.lStructSize,SIZEOF ofn
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER \
               or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
    invoke GetStartupInfo,addr startinfo
    invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, \
                  DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, \
                  NULL, NULL, addr startinfo, addr pi
    .while TRUE
       invoke WaitForDebugEvent, addr DBEvent, INFINITE
       .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
          invoke wsprintf, addr buffer, addr ExitProc, TotalInstruction
          invoke MessageBox, 0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION
          .break
       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
          .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
             mov context.ContextFlags, CONTEXT_CONTROL
             invoke GetThreadContext, pi.hThread, addr context
             or context.regFlag,100h
             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
             .continue
          .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP
             inc TotalInstruction
             invoke GetThreadContext,pi.hThread,addr context
             or context.regFlag,100h
             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE
             .continue
          .endif
       .endif
       invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, \
                                  DBG_EXCEPTION_NOT_HANDLED
    .endw
.endif
invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread
invoke ExitProcess, 0
end start

Analyse:

Ce programme ouvre une boîte de dialogue openfile. Quand l'utilisateur choisit un fichier exécutable, il exécute le programme cible en mode single-step (tracage pas à pas), et il compte le nombre d'instructions exécutées jusqu'à la sortie de ce programme cible.

       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
          .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT

Nous en profitons pour faire fonctionner le programme cible en mode single-step. Souvenez-vous que Windows envoie un EXCEPTION_BREAKPOINT juste avant qu'il n'exécute la première instruction du prog cible.

             mov context.ContextFlags, CONTEXT_CONTROL
             invoke GetThreadContext, pi.hThread, addr context

On appelle GetThreadContext pour remplir la structure CONTEXT avec les valeurs actuelles des Registres du debuggee. Plus spécifiquement, nous avons besoin de la valeur actuelle du flag REGISTER.

             or context.regFlag,100h

Nous mettons le trap bit (le 8ème bit) à 1 dans l'image du flag REGISTER.

             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
             .continue

Ainsi nous appelons la fonction SetThreadContext pour écrire par dessus les valueurs de la structure CONTEXT avec les nouvelles on appelle la fonction ContinueDebugEvent munie en plus du flag DBG_CONTINUE pour relancer le debuggee.

          .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP
             inc TotalInstruction

Quand une instruction est exécutée dans le debuggee, nous recevons le fameux 'debugging événement' (de type 'Exeption') EXCEPTION_DEBUG_EVENT. Nous devons examiner la valeur de u.Exception.pExceptionRecord.ExceptionCode. Si sa valeur est EXCEPTION_SINGLE_step, alors ce 'debugging événement' s'est produit à cause du mode Pas à Pas (single-step). Dans ce cas, nous pouvons incrémenter la variable TotalInstruction car nous savons qu'une seule et unique instruction vient d'être exécutée dans le programme cible.

             invoke GetThreadContext,pi.hThread,addr context or context.regFlag,100h
             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE
             .continue

Après que ce 'debugging exception' se soit produit on enlève (on purifie) le 'trap flag'. On doit remettre le trap flag de nouveau si on souhaite continuer en mode Pas à Pas (single-step).
Avertissement : n'employez pas l'exemple dans ce tutorial avec un grand programme : le traçage est LENT. Il vous faudrait attendre pendant dix minutes avant que vous ne puissiez fermer le debuggee.

[Iczelion's Win32 Assembly Homepage]

Taduit par Morgatte


Iczelion's Win32 Assembly Tutorial 31: ListView Control

Tutorial 31: Fichiers organisés sous la forme d'une liste
(Listview Control)

Dans ce tutorial, nous allons voir comment créer et employer un listview control.

Downloadez l'exemple.

Théorie:

Un listview control est aussi un 'common controls' (tutorial 18) tout comme le treeview (tutorial 19), richedit etc. En fait vous l'utilisez couramment sans même connaître son nom. Par exemple, la fenêtre de droite de l'Explorateur Windows est un listview control. Un listview control sert à afficher sous forme d'une liste un groupe de fichiers. En fait, c'est un peu comme une liste, mais avec certaines capacités en plus.
Vous pouvez créer un listview control de deux façons. La première méthode est aussi la plus simple : créez-le avec un éditeur de ressource. Simplement, n'oubliez pas ensuite d'appeler InitCommonControls dans votre code asm source. L'autre méthode c'est d'appeler CreateWindowEx dans votre code source. Vous devez spécifier le nom de la 'Window Class' (classe de fenêtre) correcte de ce control, c'est-à-dire SysListView32. La 'Window Class' "WC_LISTVIEW" n'est pas correcte.
Il y a quatre types de vues des données, dans un listview control : une vue sous forme d'icônes, de petites icônes, sous forme de texte inscrit et des vues sous forme de détails. Vous pouvez voir les exemples de ces vues en choisissant Affichage->Grandes icônes (la vue d'icônes), Affichage->Petites icônes (la vue en petites icônes), Affichage-> Liste (la vue de la liste) et Affichage-> Détails (la vue sous forme de détails). Ces vues sont juste une méthode de représentation des données : Elles affectent seulement la présentation des données. Par exemple, vous pouvez avoir beaucoup de données dans un listview control, mais si vous préférez, vous pouvez voir certaines d'entre elles. La vue détail est la plus informative alors que les vues restantes donnent moins de renseignements. Vous pouvez spécifier la vue que vous voulez quand vous créez un listview control. Vous pourrez même changer de vue plus tard en appelant SetWindowLong, en spécifiant flag GWL_STYLE.

Maintenant que nous savons comment créer un listview control, nous continuerons pour voir comment on peut l'employer. Je me concentrerai de la vue sous forme de détails qui fait appelle à pas mal des particularités du listview control. Les étapes dans l'utilisation d'un listview control sont les suivantes :

  1. Créez le listview control avec CreateWindowEx, et indiquez SysListView32 comme class name. Vous pouvez spécifier la vue initiale à ce niveau là.
  2. (Si elles existes) Créez et initialisez des listes d'images (Tutorial 19) pour les associer aux noms des fichiers du listview.
  3. Insérez des colonnes dans listview control. Cette étapes est nécessaire si votre listview control emploie la vue 'détails'. Explication : revenez sous l'Explorateur Windows et mettez vous en vue 'détails'. Vous voyez bien qu'il y a plusieurs colonnes, une pour le nom des fichiers, une autre pour leur date de création, une pour leur emplacement sur votre Ddur.
  4. Insérez des items et des sous-items dans votre listview control. ex: Nom, Taille ,Type ,Modifier...

Colonnes

Dans la vue 'détails', il y a une ou plusieurs colonnes. Ces données qui sont mises dans différentes colonnes (en vue détails), représentes en fait un tableau de données : les données sont arrangées dans des rangées et des colonnes. Vous devez avoir au moins une colonne dans votre listview control (uniquement vrai pour la vue 'détails'). Pour les autres vues, vous n'avez pas besoin d'insérer de colonne parce qu'il nous faut une et seulement une seule colonne pour ces vues là.
Vous pouvez insérer une colonne en envoyant LVM_INSERTCOLUMN au listview control.

LVM_INSERTCOLUMN
wParam = iCol (nombre de colonnes)
lParam = pointe sur la structure LV_COLUMN

iCol est le nombre de colonnes, en partant de 0.
LV_COLUMN contient l'information sur la colonne qui doit être insérée. Voici sa définition :

LV_COLUMN STRUCT
imask dd ?
fmt dd ?
lx dd ?
pszText dd ?
cchTextMax dd ?
iSubItem dd ?
iImage dd ?
iOrder dd ?
LV_COLUMN ENDS

Nom | Significations
imask | Est un groupe de flags indiquant que les membres dans cette structure sont valides pour cet appel. En général, ce paramètre est semblable au membre imask de la structure LV_COLUMN vue plus haut. Référez vous à votre 'référence win32 api' pour plus de détail à propos des différents flags disponibles.
iItem | Est l'index de l'item auquel cette structure se réfère. L'index de base est zéro. Vous pouvez vous représenter ce paramètre comme le numéro de la "rangée" d'un tableau.
iSubItem | Est l'index du sous-item associé à l'item indiqué par iItem ci-dessus. Vous pouvez vous représenter ce paramètre comme étant le numéro de la "colonne" d'un tableau. Par exemple, si vous souhaitez insérer un item dans un tout nouveau 'listview control' juste créé, la valeur à mettre dans iItem serait 0 (parce que cet item est le premier) et la valeur dans iSubItem serait aussi 0 (car nous voulons insérer cet item dans la première colonne). Si vous souhaitez associer un sous-item à cet item, alors le iItem représenterait l'index de l'item avec lequel vous voulez l'associer (dans cet exemple, c'est 0). Le iSubItem ne pourrait être que 1 ou plus que 1, suivant la colonne où vous voulez insérer ce sous-item. Par exemple, si votre listview control a 4 colonnes, la première colonne contiendra tous les Items. Les 3 colonnes restantes sont utilisées par les sous-items. Si vous voulez insérer un sous-item dans la 4ème colonne, vous devez mettre valeur 3 dans iSubItem.
state | Ce membre contient les flags qui reflètent l'état de l'item. L'état de l'item peut changer à cause des actions de l'utilisateur, ou bien il peut être modifié par notre programme lui-même. The state includes whether the item has the focus/is hilited/is selected for cut operation/is selected. In addition to the state flags, It can also contains one-based index into the overlay image/state image for use by the item.
stateMask | Puisque le membre 'state' ci-dessus peut contenir les flags qui définissent l'état de nos items, nous avons besoin de dire à Windows quelle valeur nous souhaitons mettre (ou remplacer si on a déjà définit son état). La valeur de ce paramètre sert à ça.
pszText | Est l'adresse de la chaîne de caractère qui sera utilisée en tant que titre pour votre item dans le cas où vous souhaitez mettre ou insérer un item. Si nous nous servons de cette structure pour récupérer la propriété d'un item donné, ce membre doit contenir l'adresse du buffer qui sera rempli avec le titre de cet item.
cchTextMax | Ce paramètre est uniquement utilisé lorsque vous employez cette structure pour recevoir des renseignements à propos de l'item. Dans ce cas, ce paramètre contient la taille en octets du buffer indiqué dans pszText, vu ci-dessus.
iImage | Est l'index d'une icône contenue dans l'imagelist. Cet index indique quelle icône doit être employé et associée avec votre item.
lParam | C'est une valeur définie par l'utilisateur, laquelle sera réutilisée lorsque vous trierez les Items de votre listview control. Bref, lorsque vous direz au listview control de trier vos Items, celui-ci comparera les Items deux par deux. Il vous renverra les valeurs des lParam des deux Items, et vous pourrez ainsi décider lequel des deux doit être inscrit en premier. Si vous êtes encore un peu dans le coaltar avec ça, ne vous inquiéter pas. Vous en apprendrez plus tard, bien plus du triage.

Ainsi après que le listview control est été créé, vous devez y insérer une ou plusieurs colonnes. Les colonnes ne sont pas nécessaires si vous ne projetez pas de faire fonctionner le listview control en vue 'détails'. Pour insérer une colonne, vous avez besoin de créer la structure LV_COLUMN, la remplir de l'information nécessaire, et indiquer le nombre de colonnes puis envoyer ensuite cette structure au listview control avec LVM_INSERTCOLUMN message.

   LOCAL lvc:LV_COLUMN
   mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
   mov lvc.pszText,offset Heading1
   mov lvc.lx,150
   invoke SendMessage,hList, LVM_INSERTCOLUMN,0,addr lvc

Ce petit bout de code vous montre comment faire. Il détermine le texte d'en-tête de la colonne ainsi que sa largeur à envoyer au listview control, grâce au message LVM_INSERTCOLUMN. C'est aussi simple que ça.

Items et sous-items

Les items sont les principales éléments dans un listview control. Dans les vues autres, que la vue 'détails', vous ne verrez que les items. Les sous-items sont les détails des items. Un unique item peut avoir un ou plus sous-items associés. Par exemple, si l'item est le nom d'un fichier, alors vous pouvez regarder (en tant que sous-items) ses attributs de fichier comme, sa taille, ou sa date de création. Dans la vue 'détails', la colonne extrême gauche contient les items et les colonnes restantes contiennent les sous-items. Vous pouvez vous représenter les items et leurs sous-items un peu comme une base de données. L'item est la clef principale du tableau alors que les sous-items sont ses éléments de description dans ce même tableau.
Au minimum, il y aura quelques Items dans votre listview control : les sous-items ne sont pas nécessaires. Cependant, si vous voulez présenter plus d'informations à l'utilisateur à propos de ces Items, vous devrez leurs associer leurs sous-items respectifs. De cette façon, l'utilisateur pourra voir les détails dans la vue 'détails'.
On insère un item dans le listview control en envoyant le message LVM_INSERTITEM. Vous avez aussi besoin de lui passer l'adresse de la structure LV_ITEM dans lParam. LV_ITEM a la définition suivante :

LV_ITEM STRUCT
  imask dd ?
  iItem dd ?
  iSubItem dd ?
  state dd ?
  stateMask dd ?
  pszText dd ?
  cchTextMax dd ?
  iImage dd ?
  lParam dd ?
  iIndent dd ?
LV_ITEM ENDS

Nom | Significations
imask | Est un groupe de flags indiquant que les membres dans cette structure sont valides pour cet appel. En général, ce paramètre est semblable au membre imask de la structure LV_COLUMN vue plus haut. Référez vous à votre 'référence win32 api' pour plus de détail à propos des différents flags disponibles.
iItem | Est l'index de l'item auquel cette structure se réfère. L'index de base est zéro. Vous pouvez vous représenter ce paramètre comme le numéro de la "rangée" d'un tableau.
iSubItem | Est l'index du sous-item associé à l'item indiqué par iItem ci-dessus. Vous pouvez vous représenter ce paramètre comme étant le numéro de la "colonne" d'un tableau. Par exemple, si vous souhaitez insérer un item dans un tout nouveau 'listview control' juste créé, la valeur à mettre dans iItem serait 0 (parce que cet item est le premier) et la valeur dans iSubItem serait aussi 0 (car nous voulons insérer cet item dans la première colonne). Si vous souhaitez associer un sous-item à cet item, alors le iItem représenterait l'index de l'item avec lequel vous voulez l'associer (dans cet exemple, c'est 0). Le iSubItem ne pourrait être que 1 ou plus que 1, suivant la colonne où vous voulez insérer ce sous-item. Par exemple, si votre listview control a 4 colonnes, la première colonne contiendra tous les Items. Les 3 colonnes restantes sont utilisées par les sous-items. Si vous voulez insérer un sous-item dans la 4ème colonne, vous devez mettre valeur 3 dans iSubItem.
state | Ce membre contient les flags qui reflètent l'état de l'item. L'état de l'item peut changer à cause des actions de l'utilisateur, ou bien il peut être modifié par notre programme lui-même. The state includes whether the item has the focus/is hilited/is selected for cut operation/is selected. In addition to the state flags, It can also contains one-based index into the overlay image/state image for use by the item.
stateMask | Puisque le membre 'state' ci-dessus peut contenir les flags qui définissent l'état de nos items, nous avons besoin de dire à Windows quelle valeur nous souhaitons mettre (ou remplacer si on a déjà définit son état). La valeur de ce paramètre sert à ça.
pszText | Est l'adresse de la chaîne de caractère qui sera utilisée en tant que titre pour votre item dans le cas où vous souhaitez mettre ou insérer un item. Si nous nous servons de cette structure pour récupérer la propriété d'un item donné, ce membre doit contenir l'adresse du buffer qui sera rempli avec le titre de cet item.
cchTextMax | Ce paramètre est uniquement utilisé lorsque vous employez cette structure pour recevoir des renseignements à propos de l'item. Dans ce cas, ce paramètre contient la taille en octets du buffer indiqué dans pszText, vu ci-dessus.
iImage | Est l'index d'une icône contenue dans l'imagelist. Cet index indique quelle icône doit être employé et associée avec votre item.
lParam | C'est une valeur définie par l'utilisateur, laquelle sera réutilisée lorsque vous trierez les Items de votre listview control. Bref, lorsque vous direz au listview control de trier vos Items, celui-ci comparera les Items deux par deux. Il vous renverra les valeurs des lParam des deux Items, et vous pourrez ainsi décider lequel des deux doit être inscrit en premier. Si vous êtes encore un peu dans le coaltar avec ça, ne vous inquiéter pas. Vous en apprendrez plus tard, bien plus du triage.

On va récapituler les étapes clef pour l'ajout d'un item ou sous-item dans le listview control.

  1. Créez une variable de type la structure LV_item
  2. Remplissez-la des informations nécessaires
  3. Envoyez le message LVM_INSERTITEM au listview control si vous voulez insérer un item. Ou si vous voulez *insert* un sous-item, envoyez LVM_SETITEM a la place. Ce sera plutôt le bordel si vous ne comprenez pas le rapport entre un item et ses sous-items. Les Sous-items représentent un peu les propriétés de l'item . Ainsi vous pouvez insérer des items, mais pas des sous-items et vous ne pouvez pas avoir un sous-item sans avoir un item qui lui soit associé. C'est pourquoi pour ajouter un sous-item vous avez besoin d'envoyer le message LVM_SETITEM au lieu de LVM_INSERTITEM.

Messages d'ordres et messages de réponses dans le ListView

Maintenant que vous savez comment créer et remplir un ListView control, l'étape suivante c'est de communiquer avec lui. Le ListView control communique avec la fenêtre parente grâce à des messages et des avis. La fenêtre parente peut commander le ListView control en lui envoyant des messages. En retour, le ListView control fait le contraire, il informe sa fenêtre parente des événements importants ou intéressants grâce à WM_NOTIFY, comme les autres 'common controls'.

Classer les items/sous-items

Vous pouvez spécifier le type de triage par défaut dans le listview control en indiquant les types de tris LVS_SORTASCENDING ou bien LVS_SORTDESCENDING dans CreateWindowEx. Ces deux types classent les Items d'après leurs noms uniquement. Si vous voulez les classer selon un autre critère, vous devez mettre le message LVM_SORTITEMS à l'intention du listview control.

LVM_SORTITEMS
wParam = lParamSort
lParam = pCompareFunction

lParamSort est une valeur définie par l'utilisateur laquelle on passera à la fonction de comparaison. Vous pouvez employer cette valeur de n'importe quelle façon, c'est à vous de voir.
pCompareFunction est l'adresse de la fonction(type de tri) que l'utilisateur à défini pour son listview control. Cette fonction a le prototype suivant :

CompareFunc proto lParam1:DWORD, lParam2:DWORD, lParamSort:DWORD

lParam1 et lParam2 sont les valeurs dans le membre de lParam de LV_item Lequel vous avez défini lorsque vous avez inséré vos Items dans le listview control.
lParamSort est la valeur de wParam que vous avez envoyée avec LVM_SORTITEMS

Quand le listview control reçoit le message LVM_SORTITEMS, il fait appel à la fonction de tri (ou de comparaison) indiquée par lParam, ainsi il nous retourne le résultat de la comparaison entre deux items. Bref, la fonction de comparaison servira à savoir lequel de deux items lui ayant été envoyé sera placé en premier par rapport à l'autre. La règle est simple : si la fonction renvoie une valeur négative, premier item (représenté par lParam1) doit être placé après l'autre. Si la fonction renvoie une valeur positive, cet item doit être placé en premier. Si les deux items sont égaux, un NULL sera renvoyé.

Ce qui fait que cette méthode fonctionne, c'est la valeur de lParam qui se trouve dans la structure LV_item. Si vous avez besoin de trier des Items (comme lorsque l'utilisateur clique sur l'en-tête d'une colonne, essayez ça marche !), vous avez besoin d'imaginer une méthode de tri qui se servira des valeurs dans le membre lParam (IParam1 et IParam2). Dans cet exemple, j'ai mis l'index de l'item dans ce paramètre donc je peux obtenir d'autres informations à propos de ces items en envoyant le message LVM_GETITEM. Remarquez que lorsque les Items sont réarrangés, leurs index changent aussi . De cette façon, quand le tri est fini dans mon exemple, j'ai encore besoin de remettre à jour les valeurs dans lParam pour retrouver les nouveaux index. Si vous voulez trier les Items lorsque l'utilisateur clique sur l'en-tête d'une colonne, vous avez besoin de traiter le message d'avis LVN_COLUMNCLICK dans votre procédure de fenêtre. On passe LVN_COLUMNCLICK à votre proc de fenêtre grâce au message WM_NOTIFY.

Exemple:

Cet exemple crée un 'listview control' et le remplit avec les noms et les tailles des fichiers du dossier actuel où vous êtes. La vue par défaut sera la vue 'détails'. Dans la vue 'détails', vous pouvez cliquer sur les en-têtes de colonne et les Items seront classés par ordre croissant/décroissant (soit par nom, soit par taille). Vous pouvez choisir la vue que vous souhaitez grâce au menu. Quand vous double cliquez sur un item, une boîte de message affichera le nom de l'item que vous venez de sélectionner.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

IDM_MAINMENU equ 10000
IDM_ICON equ LVS_ICON
IDM_SMALLICON equ LVS_SMALLICON
IDM_LIST equ LVS_LIST
IDM_REPORT equ LVS_REPORT

RGB macro red,green,blue
  xor eax,eax
  mov ah,blue
  shl eax,8
  mov ah,green
  mov al,red
endm

.data
ClassName db "ListViewWinClass",0
AppName db "Testing a ListView Control",0
ListViewClassName db "SysListView32",0
Heading1 db "Filename",0
Heading2 db "Size",0
FileNamePattern db "*.*",0
FileNameSortOrder dd 0
SizeSortOrder dd 0
template db "%lu",0

.data?
hInstance HINSTANCE ?
hList dd ?
hMenu dd ?

.code
start:
  invoke GetModuleHandle, NULL
  mov hInstance,eax
  invoke WinMain, hInstance,NULL, NULL, SW_SHOWDEFAULT
  invoke ExitProcess,eax
  invoke InitCommonControls
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
  LOCAL wc:WNDCLASSEX
  LOCAL msg:MSG
  LOCAL hwnd:HWND

  mov wc.cbSize,SIZEOF WNDCLASSEX
  mov wc.style, NULL
  mov wc.lpfnWndProc, OFFSET WndProc
  mov wc.cbClsExtra,NULL
  mov wc.cbWndExtra,NULL
  push hInstance
  pop wc.hInstance
  mov wc.hbrBackground,COLOR_WINDOW+1
  mov wc.lpszMenuName,IDM_MAINMENU
  mov wc.lpszClassName,OFFSET ClassName
  invoke LoadIcon,NULL,IDI_APPLICATION
  mov wc.hIcon,eax
  mov wc.hIconSm,eax
  invoke LoadCursor,NULL,IDC_ARROW
  mov wc.hCursor,eax
  invoke RegisterClassEx, addr wc
  invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInst,NULL
  mov hwnd,eax
  invoke ShowWindow, hwnd,SW_SHOWNORMAL
  invoke UpdateWindow, hwnd
  .while TRUE
    invoke GetMessage, ADDR msg,NULL,0,0
    .break .if (!eax)
      invoke TranslateMessage, ADDR msg
      invoke DispatchMessage, ADDR msg
  .endw
  mov eax,msg.wParam
  ret
WinMain endp

InsertColumn proc
  LOCAL lvc:LV_COLUMN

  mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
  mov lvc.pszText,offset Heading1
  mov lvc.lx,150
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc
  or lvc.imask,LVCF_FMT
  mov lvc.fmt,LVCFMT_RIGHT
  mov lvc.pszText,offset Heading2
  mov lvc.lx,100
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc
  ret
InsertColumn endp

ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD
  LOCAL lvi:LV_ITEM
  LOCAL buffer[20]:BYTE
  mov edi,lpFind
  assume edi:ptr WIN32_FIND_DATA
  mov lvi.imask,LVIF_TEXT+LVIF_PARAM
  push row
  pop lvi.iItem
  mov lvi.iSubItem,0
  lea eax,[edi].cFileName
  mov lvi.pszText,eax
  push row
  pop lvi.lParam
  invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi
  mov lvi.imask,LVIF_TEXT
  inc lvi.iSubItem
  invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow
  lea eax,buffer
  mov lvi.pszText,eax
  invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi
  assume edi:nothing
  ret
ShowFileInfo endp

FillFileInfo proc uses edi
  LOCAL finddata:WIN32_FIND_DATA
  LOCAL FHandle:DWORD

  invoke FindFirstFile,addr FileNamePattern,addr finddata
  .if eax!=INVALID_HANDLE_VALUE
    mov FHandle,eax
    xor edi,edi
    .while eax!=0
      test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY
      .if ZERO?
         invoke ShowFileInfo,edi, addr finddata
         inc edi
      .endif
      invoke FindNextFile,FHandle,addr finddata
    .endw
    invoke FindClose,FHandle
  .endif
  ret
FillFileInfo endp

String2Dword proc uses ecx edi edx esi String:DWORD
  LOCAL Result:DWORD

  mov Result,0
  mov edi,String
  invoke lstrlen,String
  .while eax!=0
    xor edx,edx
    mov dl,byte ptr [edi]
    sub dl,"0"
    mov esi,eax
    dec esi
    push eax
    mov eax,edx
    push ebx
    mov ebx,10
    .while esi > 0
      mul ebx
      dec esi
    .endw
    pop ebx
    add Result,eax
    pop eax
    inc edi
    dec eax
  .endw
  mov eax,Result
  ret
String2Dword endp

CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD
  LOCAL buffer[256]:BYTE
  LOCAL buffer1[256]:BYTE
  LOCAL lvi:LV_ITEM

  mov lvi.imask,LVIF_TEXT
  lea eax,buffer
  mov lvi.pszText,eax
  mov lvi.cchTextMax,256
  .if SortType==1
    mov lvi.iSubItem,1
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke String2Dword,addr buffer
    mov edi,eax
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke String2Dword,addr buffer
    sub edi,eax
    mov eax,edi
  .elseif SortType==2
    mov lvi.iSubItem,1
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke String2Dword,addr buffer
    mov edi,eax
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke String2Dword,addr buffer
    sub eax,edi
  .elseif SortType==3
    mov lvi.iSubItem,0
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke lstrcpy,addr buffer1,addr buffer
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke lstrcmpi,addr buffer1,addr buffer
  .else
    mov lvi.iSubItem,0
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke lstrcpy,addr buffer1,addr buffer
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke lstrcmpi,addr buffer,addr buffer1
  .endif
  ret
CompareFunc endp

UpdatelParam proc uses edi
   LOCAL lvi:LV_ITEM

   invoke SendMessage,hList, LVM_GETITEMCOUNT,0,0
   mov edi,eax
   mov lvi.imask,LVIF_PARAM
   mov lvi.iSubItem,0
   mov lvi.iItem,0
   .while edi>0
     push lvi.iItem
     pop lvi.lParam
     invoke SendMessage,hList, LVM_SETITEM,0,addr lvi
     inc lvi.iItem
     dec edi
   .endw
   ret
UpdatelParam endp

ShowCurrentFocus proc
   LOCAL lvi:LV_ITEM
   LOCAL buffer[256]:BYTE

   invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED
   mov lvi.iItem,eax
   mov lvi.iSubItem,0
   mov lvi.imask,LVIF_TEXT
   lea eax,buffer
   mov lvi.pszText,eax
   mov lvi.cchTextMax,256
   invoke SendMessage,hList,LVM_GETITEM,0,addr lvi
   invoke MessageBox,0, addr buffer,addr AppName,MB_OK
   ret
ShowCurrentFocus endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
  .if uMsg==WM_CREATE
    invoke CreateWindowEx, NULL, addr ListViewClassName, NULL, LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL
    mov hList, eax
    invoke InsertColumn
    invoke FillFileInfo
    RGB 255,255,255
    invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax
    invoke GetMenu,hWnd
    mov hMenu,eax
    invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED
  .elseif uMsg==WM_COMMAND
    .if lParam==0
      invoke GetWindowLong,hList,GWL_STYLE
      and eax,not LVS_TYPEMASK
      mov edx,wParam
      and edx,0FFFFh
      push edx
      or eax,edx
      invoke SetWindowLong,hList,GWL_STYLE,eax
      pop edx
      invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED
    .endif
  .elseif uMsg==WM_NOTIFY
    push edi
    mov edi,lParam
    assume edi:ptr NMHDR
    mov eax,[edi].hwndFrom
    .if eax==hList
      .if [edi].code==LVN_COLUMNCLICK
        assume edi:ptr NM_LISTVIEW
        .if [edi].iSubItem==1
          .if SizeSortOrder==0 || SizeSortOrder==2
            invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc
            invoke UpdatelParam
            mov SizeSortOrder,1
          .else
            invoke SendMessage,hList,LVM_SORTITEMS,2,addr CompareFunc
            invoke UpdatelParam
            mov SizeSortOrder,2
          .endif
        .else
          .if FileNameSortOrder==0 || FileNameSortOrder==4
            invoke SendMessage,hList,LVM_SORTITEMS,3,addr CompareFunc
            invoke UpdatelParam
            mov FileNameSortOrder,3
          .else
            invoke SendMessage,hList,LVM_SORTITEMS,4,addr CompareFunc
            invoke UpdatelParam
            mov FileNameSortOrder,4
          .endif
        .endif
        assume edi:ptr NMHDR
      .elseif [edi].code==NM_DBLCLK
        invoke ShowCurrentFocus
      .endif
    .endif
    pop edi
  .elseif uMsg==WM_SIZE
    mov eax,lParam
    mov edx,eax
    and eax,0ffffh
    shr edx,16
    invoke MoveWindow,hList, 0, 0, eax,edx,TRUE
  .elseif uMsg==WM_DESTROY
    invoke PostQuitMessage,NULL
  .else
    invoke DefWindowProc,hWnd,uMsg,wParam,lParam
    ret
  .endif
  xor eax,eax
  ret
WndProc endp
end start

Analyse:

La première chose que le programme fait lorsque la fenêtre principale est créée, c'est de créer notre listview control.

  .if uMsg==WM_CREATE
    invoke CreateWindowEx, NULL, addr ListViewClassName, NULL, LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL
    mov hList, eax

On appelle CreateWindowEx, en lui passant le nom de sa window class soit : "SysListView32". La vue par défaut est la vue 'détails' définie par le style LVS_REPORT.

    invoke InsertColumn

Après que le listview control est été créé, nous y insérons des colonnes.

  LOCAL lvc:LV_COLUMN

  mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
  mov lvc.pszText,offset Heading1
  mov lvc.lx,150
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc

Nous initialisons le label (son nom) et la largeur de la première colonne, qui sert au stockage des noms de fichiers, dans la structure LV_COLUMN. Donc nous avons besoin de mettre les flags LVCF_TEXT et LVCF_WIDTH dans imask . On rempli pszText avec l'adresse du label et lx avec la largeur de la colonne, en pixels. Quand tout est prêt, nous envoyons le message LVM_INSERTCOLUMN au listview control, en lui passant cette structure.

  or lvc.imask,LVCF_FMT
  mov lvc.fmt,LVCFMT_RIGHT

Quand on en a fini avec l'insertion de la première colonne, nous en insérons une autre servant cette fois-ci au stockage des tailles de fichiers. Puisque nous avons besoin des tailles pour les aligner à droite dans la colonne, nous avons besoin de mettre le flag LVCFMT_RIGHT dans le membre fmt. Nous devons aussi mettre le flag LVCF_FMT dans imask, en plus de LVCF_TEXT et LVCF_WIDTH.

  mov lvc.pszText,offset Heading2
  mov lvc.lx,100
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc

Le code restant est simple. On Met l'adresse du label (du titre de la colonne) dans pszText et sa largeur dans lx. On envoie alors le message LVM_INSERTCOLUMN au listview control, en indiquant le nombre de colonnes et l'adresse de la structure.

Quand les colonnes sont insérées, nous pouvons placer les Items dans notre listview control.

    invoke FillFileInfo

FillFileInfo présente le code suivant.

FillFileInfo proc uses edi
  LOCAL finddata:WIN32_FIND_DATA
  LOCAL FHandle:DWORD

  invoke FindFirstFile,addr FileNamePattern,addr finddata

Nous appelons FindFirstFile pour obtenir l'information sur le premier fichier qui correspond à nos critères de recherche. FindFirstFile possède le prototype de fonction suivant :

FindFirstFile proto pFileName:DWORD, pWin32_Find_Data:DWORD

pFileName est l'adresse du nom du fichier qu'on chercher. Cette chaîne de caractères peut contenir des caractères de substitutions. Dans notre exemple, nous employons *.*, ce qui fait qu'on mène une recherche sur tous les fichiers du dossier actuel où on se trouve.
pWin32_Find_Data est l'adresse de la structure WIN32_FIND_DATA laquelle sera remplie de l'information du fichier (si on l'a trouvé).

Cette fonction renvoie INVALID_handle_VALUE dans eax si aucun fichier correspondant à ce que l'on cherche n'a été trouvé. Sinon l'handle de la recherche est renvoyée, lequel sera utilisé dans les appels suivant FindNextFile.

  .if eax!=INVALID_HANDLE_VALUE
    mov FHandle,eax
    xor edi,edi

Si un fichier est trouvé, on stocke l'handle de la recherche dans une variable et ensuite nous mettons edi à zéro pour pouvoir l'employé en tant qu'index pour les Items (c'est le numéro de rangée).

    .while eax!=0
      test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY
      .if ZERO?

Dans cet exemple, je ne souhaite rien toucher dans les autres dossiers, donc je les filtre en vérifiant dwFileAttributes. Comme ça, si on trouve des fichiers qui ont un flag FILE_ATTRIBUTE_DIRECTORY associés, on saute par dessus grâce à l'appel FindNextFile.

          invoke ShowFileInfo,edi, addr finddata
          inc edi
      .endif
      invoke FindNextFile,FHandle,addr finddata
    .endw

Nous insérons le nom et la taille du fichier dans le listview control en appelant la fonction ShowFileInfo. Donc on augmente le numéro de la rangée actuelle (dans edi). Finalement on continue à appeler FindNextFile pour chercher le fichier suivant dans le dossier actuel jusqu'à ce que FindNextFile retourne un 0 (Ce qui signifie qu'il n'y a plus de fichiers à chercher).

    invoke FindClose,FHandle
  .endif
  ret
FillFileInfo endp

Quand tous les fichiers dans le dossier actuel ont étés passés à la loupe, on doit refermer l'handle de la recherche.

Maintenant on va regarder la fonction ShowFileInfo. Cette fonction accepte deux paramètres, l'index de l'item (son numéro de rangée) et l'adresse de la structure WIN32_FIND_DATA.

ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD
  LOCAL lvi:LV_ITEM
  LOCAL buffer[20]:BYTE
  mov edi,lpFind
  assume edi:ptr WIN32_FIND_DATA

On stocke l'adresse de la structure WIN32_FIND_DATA dans edi.

  mov lvi.imask,LVIF_TEXT+LVIF_PARAM
  push row
  pop lvi.iItem
  mov lvi.iSubItem,0

Nous donne le nom de l'item et sa valeur dans lParam donc nous mettons les flags LVIF_TEXT et LVIF_PARAM dans imask. Ensuite nous plaçons l'item dans la bonne rangée (dont on avait passé son numéro à la fonction) et puisque c'est l'item principal, on doit mettre un 0 dans iSubItem (car la première colonne, c'est la colonne 0).

  lea eax,[edi].cFileName
  mov lvi.pszText,eax
  push row
  pop lvi.lParam

Ensuite on met l'adresse de son nom, (dans ce cas), le nom du fichier dans pszText de la structure WIN32_FIND_DATA. Puisque nous effectuons un tri dans le listview control, nous devons remplir lParam avec une valeur. Je souhaite mettre le numéro de rangée dans ce membre donc je peux me servir des renseignements de l'item grâce à son index pour le retrouver.

  invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi

Lorsque tous les paramètres nécessaires dans LV_item sont remplis, on envoie le message LVM_INSERTITEM au listview control pour y insérer un item.

  mov lvi.imask,LVIF_TEXT
  inc lvi.iSubItem
  invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow
  lea eax,buffer
  mov lvi.pszText,eax

On place, dans la deuxième colonne, le sous-item associé à l'item qu'on vient juste d'insérer. Un sous-item peut seulement posséder un label (un titre). Donc on met LVIF_TEXT dans imask. Ensuite dans iSubItem, on indique la colonne dans laquelle le sous-item doit se trouver. Dans notre cas, nous met iSubItem à 1 en l'incrémentant. Le label que nous utiliserons représentera 'la taille du fichier'. Cependant, nous devons d'abord le convertir en une chaîne de caractères en appelant wsprintf. C'est pourquoi nous mettons l'adresse de la chaîne de caractères dans pszText .

  invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi
  assume edi:nothing
  ret
ShowFileInfo endp

Quand tous les paramètres nécessaires à LV_item sont remplis, nous envoyons le message LVM_SETITEM à notre listview control, en lui passant l'adresse de la structure LV_ITEM. Remarquez que nous utilisons LVM_SETITEM, et non pas LVM_INSERTITEM parce qu'un sous-item est considéré comme étant une propriété de l'item. Donc rappelez vous de ça, c'est important, on *pose* (set) la propriété de l'item, mais on n'insère pas un nouvel item.

Lorsque tous les Items sont insérés dans le listview control, on défini le type de texte et les couleurs de fond de notre listview control.

    RGB 255,255,255
    invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax

J'emploie la macro RGB pour convertir les valeurs rouge, verte, est bleue dans eax et les employer pour définir la couleur réelle que je souhaite obtenir. On défini les couleurs de premier plan et d'arrière plan du texte avec les messages LVM_SETTEXTCOLOR et LVM_SETTEXTBKCOLOR. On défini la couleur de fond de notre listview control en lui envoyant le message LVM_SETBKCOLOR.

    invoke GetMenu,hWnd
    mov hMenu,eax
    invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED

On laisse l'utilisateur choisit ses vues grâce au menu. Donc on doit d'abord récupérer l'handle du menu. Pour aider l'utilisateur à retrouver la vue actuelle, on incorpore un système de bouton radio dans notre menu. L'item du menu qui est associé à la vue actuelle sera ainsi précédé d'un bouton tout rond. Pour faire ça, on doit appeler CheckMenuRadioItem. Cette fonction affichera un bouton radio juste devant l'item voulu dans votre menu.

Remarquez qu'on crée un listview control d'une largeur et d'une hauteur égales à 0. Elle sera redimensionnée plus tard à chaque fois que la fenêtre parente sera elle-même redimensionnée. De cette façon, on peut être sûr que la taille de notre listview control correspondra toujours à celle de la fenêtre parente. Dans notre exemple, on souhaite que le listview control remplisse la totalité du secteur de client de la fenêtre parente.

  .elseif uMsg==WM_SIZE
    mov eax,lParam
    mov edx,eax
    and eax,0ffffh
    shr edx,16
    invoke MoveWindow,hList, 0, 0, eax,edx,TRUE

Quand la fenêtre parente reçoit le message WM_SIZE, le mot de poids faible de lParam contient la nouvelle largeur du secteur client et le mot de poids fort la nouvelle hauteur. Ensuite on appelle MoveWindow pour redimensionner le listview control pour qu'il recouvre le secteur client de la fenêtre parente, en entier.

Lorsque l'utilisateur sélectionne une des vue dans le menu. On doit faire en sorte de changer la vue du listview control en conséquence. On fait ça en mettant un nouveau style dans le listview control avec SetWindowLong.

  .elseif uMsg==WM_COMMAND
    .if lParam==0
      invoke GetWindowLong,hList,GWL_STYLE
      and eax,not LVS_TYPEMASK

La première chose à faire, c'est obtenir les styles actuels du listview control. Donc on purifie le vieux type de vue renvoyé dans les flags. LVS_TYPEMASK est une constante qui représente la valeur combinée des 4 types de vue (LVS_ICON+LVS_SMALLICON+LVS_LIST+LVS_REPORT). Ainsi quand on exécute l'opération and sur les types actuels de flags avec la valeur "not LVS_TYPEMASK", ça équivaut à nettoyer le type de vue actuel.

Dans la conception du menu, je triche un peu. J'emploie le type de vue des constantes en tant qu'IDs du menu.

IDM_ICON equ LVS_ICON
IDM_SMALLICON equ LVS_SMALLICON
IDM_LIST equ LVS_LIST
IDM_REPORT equ LVS_REPORT

Ainsi quand la fenêtre parente reçoit le message WM_COMMAND, le type de vue voulu est dans le mot de poids faible de wParam pour représenter l'ID du menu.

      mov edx,wParam
      and edx,0FFFFh

On retrouvera le type de vue souhaité dans le mot de poids faible de wParam. Tout ce que nous devons faire c'est de mettre le mot de poids fort à 0 (je parle de wParam).

      push edx
      or eax,edx

Et ajoutez le type de vue désiré aux styles déjà existants (moins le style de vue actuel) du listview control.

      invoke SetWindowLong,hList,GWL_STYLE,eax

Et mettez les nouveaux types avec SetWindowLong.

      pop edx
      invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED
   .endif

Nous avons aussi besoin de mettre le bouton radio devant l'item choisi dans notre menu. Ainsi on appelle CheckMenuRadioItem, en lui passant le type de vue actuel (en double, en tant qu'ID du menu).

Lorsque l'utilisateur clique sur l'en-tête de la colonne dans la vue 'détails', on fait en sorte de déclencher le comportement de triage des Items dans notre listview control. Nous devons répondre au message WM_NOTIFY.

  .elseif uMsg==WM_NOTIFY
    push edi
    mov edi,lParam
    assume edi:ptr NMHDR
    mov eax,[edi].hwndFrom
    .if eax==hList

Quand on reçoit le message WM_NOTIFY, lParam contient le pointer qui est sur la structure NMHDR. On peut vérifier si ce message provient de notre listview control en comparant le membre hwndFrom dans NMHDR à l'handle de notre listview control. S'ils correspondent, alors on est sûr que cet avis provient bien de notre listview control.

      .if [edi].code==LVN_COLUMNCLICK
        assume edi:ptr NM_LISTVIEW

S'il provient bien du listview control, on vérifie si le code est LVN_COLUMNCLICK. Et si c'est ça, ça signifie que l'utilisateur a cliqué sur l'en-tête de la colonne. Dans le cas où le code serait LVN_COLUMNCLICK, on est sûr que lParam contient le pointer qui est sur la structure NM_LISTVIEW laquelle est un 'superset' de la structure NMHDR. On aura alors besoin de savoir sur lequel des en-tête de colonne, l'utilisateur à cliqué. L'examen du membre iSubItem nous révèle ces renseignements. La valeur dans iSubItem peut être considérée comme le numéro de colonne, en commençant par 0 pour la première colonne.

        .if [edi].iSubItem==1
          .if SizeSortOrder==0 || SizeSortOrder==2

Dans le cas où iSubItem est égal à 1, ça signifie que l'utilisateur à cliqué sur la deuxième colonne, (la taille). On utilise des variables d'état pour récupérer le statut actuel de l'ordre de triage. 0 signifie "Pas encore trié", 1 signifie qu'"on tri l'item en le faisant monté d'une place dans la hiérarchie", 2 signifie qu'"on tri l'item en le faisant descendre dans la hiérarchie" des items. Si les Items ou sous-items de la colonne n'ont pas été trié auparavant, ou on été trié en ayant perdu des places, on impose un tri pour les remonter.

            invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc

On envoie le message LVM_SORTITEMS à notre listview control, en lui passant 1 dans wParam et l'adresse de notre fonction de tri dans lParam. Remarquez que la valeur dans wParam est définie par l'utilisateur, vous pouvez l'employer de la façon que vous le souhaitez. Je l'emploie en tant que méthode de triage dans cet exemple. Bon, on va d'abord jeter un coup d'oeil à la fonction de tri.

CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD
  LOCAL buffer[256]:BYTE
  LOCAL buffer1[256]:BYTE
  LOCAL lvi:LV_ITEM

  mov lvi.imask,LVIF_TEXT
  lea eax,buffer
  mov lvi.pszText,eax
  mov lvi.cchTextMax,256

Dans la fonction de tri, le listview control passera les lParams (provenant de LV_item) de deux Items dont il a besoin de comparer pour les triés l'un par rapport à l'autre, soit lParam1 et lParam2. Vous vous rappellerez qu'on a mis l'index des l'items dans lParam. Ainsi on peut récupérer l'information des Items en interrogeant le listview control en employant ces index. Les renseignements dont nous avons besoin sont les labels (leurs noms) des Items / sous-items étant triés. Ainsi on prépare la structure LV_ITEM dans ce but, en déclarant LVIF_TEXT dans imask et l'adresse du buffer dans pszText et la taille du buffer dans cchTextMax.

  .if SortType==1
    mov lvi.iSubItem,1
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi

Si la valeur dans SortType est 1 ou 2, on sait que la colonne (de taille) a été cliquée. 1 signifie que les Items ont été triés selon leurs tailles dans un ordre montant. 2 signifie le contraire. Ainsi, on impose iSubItem à 1 (pour spécifier la colonne de taille) et on envoie le message LVM_GETITEMTEXT à notre listview control pour obtenir le label (le nom) de la chaîne de caractères (de taille) du sous-item.

    invoke String2Dword,addr buffer
    mov edi,eax

On cache plus ou moins la taille de la chaîne de caractères dans une valeur Dword avec la fonction String2Dword que j'ai écrite. Elle renvoie cette valeur Dword dans eax. Et en plus on la stocke dans edi pour la trier par la suite.

    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke String2Dword,addr buffer
    sub edi,eax
    mov eax,edi

Faites de même avec la valeur dans lParam2. Quand nous avons les tailles des deux fichiers, nous pouvons alors les comparer.
La règle de la fonction de tri est définie ainsi :

  • Si le premier item doit être placé avant l'autre, vous devez renvoyer une valeur négative dans eax
  • Si le premier item doit être placé après le second, vous devez renvoyer une valeur positive dans eax
  • Si les deux Items sont égaux, on renvoie un zéro dans eax.

Dans ce cas, on souhaite trier les Items selon leurs critères de tailles par ordre croissant. Donc on peut utiliser la ruse d'une simple soustraction des tailles des deux items et renvoyer le résultat dans eax.

  .elseif SortType==3
    mov lvi.iSubItem,0
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke lstrcpy,addr buffer1,addr buffer
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke lstrcmpi,addr buffer1,addr buffer

Dans le cas où l'utilisateur clique sur la colonne 'de nom de fichier', nous devons forcément comparer les noms des fichiers. Il nous faut d'abord récupérer les noms de fichier et on les compare ensuite avec la fonction 'lstrcmpi'. On peut renvoyé la valeur de retour de lstrcmpi sans aucune modification puisqu'elle emploie aussi la même règle de tri. C'est à dire qu'on aura une valeur négative dans eax si la première chaîne de caractères est moindre (B<A...) que la deuxième.

Une fois que les Items ont été triés, nous avons besoin de remettre à jour les valeurs de lParam de chaques Items pour refléter les nouveaux index en appelant la fonction UpdatelParam.

            invoke UpdatelParam
            mov SizeSortOrder,1

Cette fonction énumère simplement tout les Items du listview control et met à jour les valeurs dans lParam avec les nouveaux index. On doit absolument faire ça sinon le tri suivant ne marchera pas comme attendu, parce qu'on suppose que la valeur dans lParam est l'index de l'item.

      .elseif [edi].code==NM_DBLCLK
        invoke ShowCurrentFocus
      .endif

Quand l'utilisateur double clique sur l'item, on souhaite que son nom s'affiche dans une boîte de message. On doit donc vérifier si le code dans NMHDR est NM_DBLCLK. Si c'est ça, on peut continuer pour récupérer son nom et le montrer dans la MessageBox.

ShowCurrentFocus proc
   LOCAL lvi:LV_ITEM
   LOCAL buffer[256]:BYTE

   invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED

Comment savons-nous quel item vient d'être double cliqué ? Et bien, lorsque un item est cliqué ou double cliqué, son état est remis à "jour". Même si beaucoup d'Items sont (hilited) sélectionnés avec un encadré pointillé, uniquement un d'entre eux a le centre (est réellement sélectionné). Notre travail, c'est de retrouver l'item qui a le centre. Nous faisons ça en envoyant le message LVM_GETNEXTITEM à notre listview control, en spécifiant l'état désiré dans lParam. -1 dans wParam signifie qu'on recherche tous les Items. L'index de l'item est renvoyé dans eax.

   mov lvi.iItem,eax
   mov lvi.iSubItem,0
   mov lvi.imask,LVIF_TEXT
   lea eax,buffer
   mov lvi.pszText,eax
   mov lvi.cchTextMax,256
   invoke SendMessage,hList,LVM_GETITEM,0,addr lvi

On continue alors pour récupérer son titre (son nom) en envoyant le message LVM_GETITEM au listview control.

   invoke MessageBox,0, addr buffer,addr AppName,MB_OK

Finalement, nous affichons le nom du fichier dans une MessageBox.

Si vous souhaitez savoir comment utiliser des icônes dans votre listview control, vous pouvez lire ça dans mon tutorial sur le treeview (Tutorial 19). Les étapes sont à peu près les mêmes.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Vocabulaire

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

ASSEMBLAGE (Phase d') : Il s'agit en fait de savoir comment créer un programme à partir d'un fichier texte nommé *.asm
Voilà, vous avez un fichier nommé : ESSAI.ASM
Certains auront en plus un fichier de resource:
- il peut prendre le même nom que son fichier ESSAI.RC
- Ou bien un nom défini par défaut RSRC.RC

Ces deux fichiers (ou bien seulement ESSAI.ASM si il n'a pas de ressources) doivent êtres placés dans un dossier où se trouvent aussi les fichiers suivants:
- ML.exe
- RC.exe (+Rcdll.dll)
- LINK.exe (+Mspdb50.dll)

ensuite il suffit d'écrire sous dos:
C:\......\ml /c /coff /Cp ESSAI.ASM
C:\......\rc ESSAI.RC (ou bien : rc RSRC.RC)
C:\......\link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm32\lib ESSAI.obj ESSAI.res
(ou bien RSRC.res) Attention à ce 'c', si vous avez installés masm32 sur d: devinez ce que vous devez faire.

Pour vous aider, j'ai automatisé tout ça dans un petit programme en *.bat qui s'appelle 'Assemble_le.bat'. Il s'occupera de tout pour vous, à cette condition près: Vous installez masm32 sur c: et créer votre sous dossier spécial assemblage (peu importe son nom) sous masm32. Ce qui pour moi donne c:\masm32\_essai\ Dans ce dossier vous devez forcément mettre ml, rc, link, Cvtres et les deux dll. Placez y aussi Assemble_le.bat et votre fichier à assembler sous le nom unique de essai.asm, si vous avez un fichier de ressource en plus il doit aussi s'y trouver et s'appelé essai.rc ou rsrc.rc.

Le même processus peut être entrepris pour ASSEMBLER UNE DLL.

CHILD WINDOW :
Voir 'Control'.

Classe de fenêtre
Voir 'Window Class'

COMMAND LINE
Le paramètre lpCmdLine est un pointeur vers la ligne de commande ayant executé l'application (du style "c:\monapp.exe /v"), ce qui est utile pour récupérer des paramètres en ligne de commande. Vous obtiendrez ce pointeur en appelant la fonction GetCommandLineA.
(info pêchée dans un Tut sur Nasm d'X-or)

Control
Control n'est pas une traduction du mot français Contrôle, rien à voir. Un Control est un objet. Par exemple vous avez créé votre fenêtre principale, si vous y ajoutez une boite de saisie de texte, des boutons, une barre de scrolling verticale et horizontale (pour faire défiler un texte plus grand qu'une page), tout ça sont des 'Controls' ou bien des 'Child Window'. D'ailleurs, dans certains tuts vous trouverez: 'Child Window Controls'. Donc quand on parle d'un 'Child Window' ou une 'Fenêtre Enfant' ce n'est pas une fenêtre comme on l'entend de la même manière que notre fenêtre principale mais c'est un objet quelconque un 'Control' qui est commandé par une vraie fenêtre ou bien même un autre Control mais de plus haute autorité. Comme la 'fenêtre principale' est dite 'la fenêtre parente' de tous les sous objets tels que les boutons qui la composent... qui sont ses objets enfants ou ses 'Child Window'

handle
Sachez qu'un handle est une sorte d'index dans un tableau contenant des caractèristiques. Ici windows tient un tableau d'info sur le programme et notre numéro d'handle lui permet de savoir ou regarder dans son tableau pour avoir le numéro qu'on lui donne.
(info pêchée dans un Tut sur Nasm d'X-or)

Chaque objet possède son propre et unique Handle, la fenêtre principale ses fenêtres enfants, les boutons les boîtes d'édition...etc. Un Handle, c'est le n° qui sert à faire appel à un objet. Un programme se sert des handles pour savoir comment sont liés les objets les uns par rapport aux autres. Par exemple une fenêtre pincipale portant le Handle=0054 ouvre une MessageBox qui aura comme dernier paramètre 0054 qui représente le handle de son parent. Ainsi on sait a qui appartient cette MessageBox.
Si on a récupéré du texte dans un Buffer (un espace de mémoire pour stocker des infos) et qu'on souhaite envoyer ce texte vers un receveur, par exemple une boîte d'édition, une fenêtre fille, ou un bouton ou la fenêtre principale, Alors on doit envoyer ce texte vers le Handle du bouton pour que le texte de ce bouton soit remplacé par le nouveau. Sans Handle, ce texte n'aurait pas su à quel objet il était destiné.

hInstance
Le paramètre hInstance est un nombre identifiant le programme en cours d'execution. hInstance signifie 'handle Instance', sachez qu'un handle est une sorte d'index dans un tableau contenant des caractèristiques. Ici windows tient un tableau d'info sur le programme et notre numéro d'handle lui permet de savoir ou regarder dans son tableau pour avoir le numéro qu'on lui donne. Vous pouvez l'obtenir en appelant la fonction GetModuleHandleA qui renvoie l'Handle (nb) correspondant à la tache en cours. Notez que sous win32, Handle=hInstance.
(info pêchée dans un Tut sur Nasm d'X-or)

HUTCH : Le hutch est l'ensemble des fichiers *.inc . Tous les fichiers INCLUDE font parti du hutch.

-------------------------------------------------------
| THE HUTCH |
| |
| |--------------------------------------------| |
| | Fichier INCLUDE n°1 (extension *.inc) | |
| |--------------------------------------------| |
| |
| |--------------------------------------------| |
| | Fichier INCLUDE n°2, n°3, n°4, n°...etc | |
| |--------------------------------------------| |
| |
| |
| |--------------------------------------------| |
| | Windows.inc est aussi un fichier | |
| | INCLUDE (c'est le principal fichier du hutch) | |
| |--------------------------------------------| |
| |
-------------------------------------------------------

MOT : (Le mot de poids faible ou de poids fort)
Par exemple si LParam est une variable de 32 bit (un DWord)
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
et qu'on rempli le mot (the Word donc 16 bit) de poids fort de LParam on aura ça:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Si au contraire on avait rempli le mot de poids faible de LParam on aurait eu ceci:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

En fait, quand on change la valeur du mot de poids fort de LParam, on ne met pas que des 1 mais une suite de 1 et de 0, mais surtout ça signifie qu'on ne touche en aucun cas à sa partie basse. Et inversement.

PROCEDURE (de fenêtre, de messages, de debugging<-- j'arrive pas à traduire ça) :
La 'procédure de fenêtre' est la procédure principale de notre programme. En fait, c'est un bout de code qui forme une boucle sur lui-même et qui s'occupe d'afficher tous les éléments à l'intérieur de notre fenêtre (il gère les boutons...)
La 'procédure de messages' c'est en fait aussi la même chose mais en plus, on fait en sorte qu'elle s'occupe de traiter des messages quand l'un d'eux lui parvient. Ex: Si on clique sur le bouton, elle le détecte et on peut utiliser cette information.
La 'procédure de debugging' c'est encore pareil, c'est une procédure de messages quelconque à ceci prêt que les seuls messages qui nous intéressent dans ce cas sont des messages provoqués par des événement qui sont spécifiques à tous ce qui touche le débogage. (Genre : Ouverture d'un nouveau lien, création d'un process, lire ou écrire dans un octet d'un autre programme...etc)

PROTOTYPE DE FONCTION : Le prototype d'une fonction n'est en aucun cas une instruction du language Assembleur. ça n'a rien à voir avec des instructions comme 'Mov', 'Push', 'Call'... c'est simplement une notice pour qu'on sache comment utiliser une fonction. Mais jamais on écrira un prototype de fonction dans le programme lui-même.
Exemple: Le 'Prototype de Fonction' de 'ExitProcess' est ExitProcess proto uExitCode:DWORD. ce qui signifie que la fonction ExitProcess s'utilise avec une seule valeur de type Dword. D'après ce qu'on vient d'appendre de son prototype de fonction juste au-dessus, vous devrez écrire dans votre programme un truc dans le genre :
invoke ExitProcess, 0 (Si vous faites appelle à un fichier INCLUDE)
Ou bien:
Push 0 (Si vous ne faites pas appelle à un fichier INCLUDE)
Call ExitProcess (ça, c'est de l'assembleur tout ce qu'il y a de plus classique)

WINDOW CLASS :
Une classe de fenetre est une structure WNDCLASSEX (EX pour extended, vous pouvez utiliser la structure WNDCLASS mais elle propose moins d'options) contenant toutes les informations concernant la fenetre : sa taille, ses attributs, la fonction chargée de gérer les messages qui lui sont envoyés, etc... Vous commencez donc par la remplir.
Ensuite, vous l'enregistrez avec la fonction RegisterClassExA (respectivement RegisterClass), vous créez la fenetre avec la fonction CreateWindowExA, vous l'affichez avec la fonction ShowWindow et vous rentrez dans la boucle des messages. Cette dernière est arrêtée par l'envoi d'un message de fermeture à l'application. Le rôle de cette boucle est de demander à windows de lui envoyer un message de la liste d'attente (par la fonction GetMessageA), de le traduire (par la fonction TranslateMessage qui par exemple traduit les scan codes du clavier en codes ASCII) et de l'envoyer à la fenetre de l'application à laquelle elle est destinée (par la fonction DispatchMessageA).
(info pêchée dans un Tut sur Nasm d'X-or)


ASSEMBLER UNE DLL

Si vous avez parcouru les Tuts d'Aczelion, vous avez dû voir dans le Tut 17 comment programmer vos propres DLL (Dynamic Link Libraries), il donne également une brève explication pour les assembler mais revenons sur ce point. Je ne vais pas m'attarder de trop, on va passer à la pratique, ça vaut bien toutes les explications.

Faites l'exemple avec moi:

Je pratique sur la DLL du Tut24 'MouseHook.asm'. D'abord il faut étudier comment elle a été construite et c'est extrêmement important. Car pour compiler une DLL il faut deux fichiers. le fichier *.asm et le fichier *.def qui en découle.

;--------------
;MouseHook.asm
;--------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib

.const
WM_MOUSEHOOK equ WM_USER+6
.data
hInstance dd 0

.data?
hHook dd ?
hWnd dd ?

.code
DllEntry proc hInst:HINSTANCE, reason:DWORD, reserved1:DWORD ;<-- on commence toujours pas
    push hInst				   		; déclarer le point d'entrée
    pop hInstance					  	; de la DLL.
      mov  eax,TRUE						; Mais peu importe...
      ret
DllEntry Endp

MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD	; Ce qui nous intéressent ce sont les
    invoke CallNextHookEx,hHook,nCode,wParam,lParam	; noms des fonctions appartenant à la
    mov edx,lParam					; DLL
    assume edx:PTR MOUSEHOOKSTRUCT
    invoke WindowFromPoint,[edx].pt.x,[edx].pt.y
    invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0
    assume edx:nothing
    xor eax,eax
    ret
MouseProc endp

InstallHook proc hwnd:DWORD
    push hwnd
    pop hWnd
    invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
    mov hHook,eax
    ret
InstallHook endp

UninstallHook proc
    invoke UnhookWindowsHookEx,hHook
    ret
UninstallHook endp

End DllEntry

Et voilà à partir du contenu de la DLL on va pouvoir créer le fichier *.def. Puisqu'on sait que notre MouseHook.asm contient les fonctions MouseProc, InstallHook et UninstallHook, le fichier *.def sera ainsi définit:
;-------------
;MouseHook.def
;-------------
LIBRARY MouseHook  ; <-- c'est le nom de votre DLL

EXPORTS		   ; <-- et les fonctions qu'elle met à notre disposition.
  MouseProc
  InstallHook
  UninstallHook
Voilà vous écrivez ça dans MouseHook.def

Maintenant nous avons les deux fichiers en question (*.asm et *.def), nous pouvons passer à la phase d'assemblage de la DLL en elle-même.

D'abord sachez que j'ai créé un dossier spécial assemblage en tout genre dans mon dossier Masm32. Mon dossier s'appelle C:\Masm32\_essai et dedans se trouvent les petits outils indispenssables que sont :
- ML.exe
; - RC.exe (+Rcdll.dll)
; ça c'est pour passer - LINK.exe (+Mspdb50.dll)
; d'un *.asm --> *.exe - Cvtres.exe
; Normalement ils sont présent dans c:\Masm32\bin
Pour une DLL vous devez en plus rajouter:
- Lib.exe

Allons-y gaiement:
J'ouvre une fenêtre DOS et je me déplace dans ce répertoire:
cd\
cd Masm32
cd _essai
Pour vous se sera normalement cd bin

ET VOICI LA VÉRITABLE OPÉRATION:

(Les majuscules et minuscules n'ont pas d'importance sur le nom de votre DLL mais elles en ont pour le reste. Attention au paramètre /Cp par exemple.)
ml /c /coff /Cp mousehook.asm
link /DLL /NOENTRY /def:mousehook.def /subsystem:windows mousehook.obj

J'ai automatisé ce processus dans un fichier *.bat nommé Assembles_DLL.bat. Vous pourrez vous en servir en plaçant Assembles_DLL dans ..C:\Masm32\Bin ou pour moi ..C:\Masm32\_essai. Placez dans le même répertoire vos deux fichiers *.asm et *.def sous le nom de 'DLL.asm' et 'DLL.def'. Exécutez 'Assembles_DLL' et vous obtiendrez une DLL du nom de (Et non pas DLL.dll) Nom_qui_est_dans_DLLdef.dll. Il sera donc inutile de la renommer.

Par Morgatte


Tutorial Window

- Chap.1: Création d'une fenêtre -

Ce chapitre risque de vous rebuter quelque peu, mais il faut savoir qu'en fait, il est le plus difficile à assimiler. Cependant je vais vous faciliter la tâche. En effet seront présentées ici des fonctions comportant parfois une douzaine d'arguments. Je prendrai alors le parti suivant: celui de ne pas documenter les arguments qui servent rarement. Si le coeur vous en dit, vous pourrez toujours vous y intéresser à travers la documentation Microsoft.

De même, il faut savoir que le squelette présenté ici sert quasiment pour 90 % des applications. Il semble donc plutôt évident que s'en servir comme base d'un programme est la meilleure façon de commencer un projet.

- 1.1: Fonctionnement d'une application -

Avant toute chose, il faut connaître la manière dont se comporte une application Windows. Celle-ci est identifiée par une variable nommée "variable d'instance". Le type donné par Windows est HINSTANCE. Nous pourrons par exemple déclarer une variable d'instance par :

  hInstance HINSTANCE ?

Où 'hInstance' est le nom qu'on aurra choisi pour identifié cet instance plus tard, alors que 'HINSTANCE' est le type de cette variable. On peut donc choisir un autre nom que 'hInstance'. Tel que:
hInstanceProgPrincipal HINSTANCE ?

Une application est composée d'une fenêtre principale, laquelle contient des fenêtre filles, lesquelles peuvent elles-même contenir des fenêtres filles, etc ... La dépendance mère/fille se matérialise par la propagation de la prise de focus, de la destruction des fenêtres filles quand la fenêtre mère est détruite, etc ... (il serait en effet bizarre par exemple que Notepad soit quitté et que le contrôle d'édition de texte reste flottant). Chacune de ces fenêtres sont-elles aussi identifiées par une variable nommée "handle". Le type donné par Windows est HWND. Nous pourons par exemple récupérer le handle d'une fenêtre à l'aide d'une variable déclarée comme suit :

  LOCAL hwnd:HWND

Ici encore c'est bien 'hwnd' qui est la variable alors que 'HWND' est le type de variable. Ici on déclara donc la variable 'hwnd' (qu'on aurait pu appeler 'HandleFenetrePrincipale' par exemple) en tant que variable de type: handle. On aurrait donc tout aussi bien pu mettre,
LOCAL HandleFenetrePrincipale:HWND

Voilà tout pour la manière dont Windows identifie tout son beau monde. Plus tard nous apprendrons que Windows identifie le reste de son environnement à l'aide de contexte de périphérique (imprimante, clavier, affichage zone écran, etc ...).

C'est bien beau de savoir comment Windows identifie les fenêtres, mais comment permettre à l'application de dialoguer avec l'OS et les autres fenêtres ? Et bien tout simplement à l'aide d'envoi de messages. Ceux-ci sont destinés soit à Windows, soit à une autre fenêtre. Toute fenêtre a alors un comportement défini face à la réception de ceux-ci.
Par exemple, prenons une fenêtre texte d'handle hWndText affichant le résultat d'une compilation. Celui-ci devient visible par clik sur un bouton "Afficher Resultat" d'handle hWndButton. Il faut donc que lorqu'un clik se produise sur le bouton, un message soit envoyé à la fenêtre texte pour qu'elle affiche le résultat ! Ces envois se font à l'aide de la fonction SendMessage() que nous détaillerons plus loin. Pour info, nous aurons dans notre cas :

  invoke SendMessage, hWndText, WM_SETTEXT,IDM_GETTEXT,0

Reste à savoir que nombre de messages sont générés par Windows lui-même, ou situés dans le code des contrôle communs comme le simple clik d'un bouton qui envoi le message WM_CLICK.

- 1.2: Inclusions et prototypes -

Première chose à savoir, les inclusions nécessaires. Déclarez toujours 'windows.inc'. Par contre vous n'aurez besoin de déclarer 'user32.inc' uniquement si vous faites appel à une de ses fonctions API (tel que SetMessage...) c'est la même chose pour Kernel (ex: ExitProcess) et les autres...

  include \masm32\include\windows.inc

  include \masm32\include\user32.inc
  include \masm32\include\kernel32.inc
  includelib \masm32\lib\user32.lib
  includelib \masm32\lib\kernel32.lib
  ...

C'est quand même beau la vie ! Vous avez alors à disposition tout le nécessaire pour programmer. Bien qu'il soit possible d'utiliser les fonctions C-ANSI, il est préférable de s'adapter à Windows, à l'aide des fonctions propres à Windows (comme "lstrcmp()" au lieu de "strcmp()", ou encore "GlobalAlloc(GPTR, ...)" au lieu de "malloc(...)").

Ensuite le prototype principal. Pas de "int main(int argc, char **argv);", mais celui-ci :

  WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

Où HINSTANCE, LPSTR, et DWORD représentent les types de ces données.
hInst et hPrevInst seront dont deux handles d'instances
CmdLine sera donc une chaîne de caractères (du texte en fait)
et CndShow un nombre de dimension DWORD donc sur 32bit (4 octets)

Voici le détail des arguments passés par windows au lancement de l'application :

- hInst : la variable d'instance de l'application (Windows nous le donne gentillement)
- hPrevInst : c'est rigolo, çà sert à rien en fait :) (toujours 0)
- CmdLine : Une chaine représentant les arguments en ligne (utile pour récupérer le nom d'un fichier lors d'un double-clik sur celui-ci lorsque son extension est associée à notre programme).
- cmdShow : La manière dont notre application doit être affichée (minimisée, normale, agrandie ...)

Il est utile de récupérer la variable d'instance. Notre programme basé sur nos connaissances actuelles a donc cette forme :

  ...
  ...
  ...
  include \masm32\include\windows.inc

  include \masm32\include\user32.inc
  include \masm32\include\kernel32.inc
  includelib \masm32\lib\user32.lib
  includelib \masm32\lib\kernel32.lib

  WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

  .DATA
   ...
   ...

  .DATA?
  hInstance HINSTANCE ?
  CommandLine LPSTR ?
   ...
   ...

  .CODE
   ...
   ...
   ...
  invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
   ...
   ...

- 1.3: Classes de fenêtres -

Nous allons maintenant créer notre fenêtre. Une fenêtre est un objet, et comme tout objet (pour peu que vous connaissiez la programmation objet), celui-ci est instancié à partir d'une classe. Pour ceux qui ne maitrisent pas ce genre de concept, il s'agit en fait d'un "moule" à partir duquel notre fenêtre saura prendre forme (si vous êtes rebutez, partez du principe qu'une classe est une sorte de structure dont les valeurs définnisent la "forme" des objets qui s'y réfèrent).
Pour cela nous vient en aide une classe de fenêtre. Il nous faut donc initialiser certaines de ses variables (je réserve le terme de "propriété" aux experts). Il est préférable de le faire à l'aide d'une fonction pour la lisibilité du code. Sachez, avant la présentation du programme, que le type "classe de fenêtre" sous Windows est WNDCLASS.

Voici notre fonction d'initialisation d'une classe de fenêtre :

 WNDCLASSEX STRUCT DWORD
  cbSize            DWORD      ?	   ;size = SIZEOF WNDCLASSEX
  style             DWORD      ?	   ;style = 0 ou CS_HREDRAW
  lpfnWndProc       DWORD      ?	   ;lpfnWndProc = WindowProcedure (OFFSET WndProc)
  cbClsExtra        DWORD      ?	   ;cbClsExtra = 0
  cbWndExtra        DWORD      ?	   ;cbWndExtra = 0
  hInstance         DWORD      ?	   ;hInstance = hInst
  hIcon             DWORD      ?	   ;hIcon = invoke LoadIcon....
  hCursor           DWORD      ?	   ;hCursor = invoke LoadCursor....
  hbrBackground     DWORD      ?	   ;hbrBackground = COLOR_3DFACE +  1
  lpszMenuName      DWORD      ?	   ;lpszMenuName = 0
  lpszClassName     DWORD      ?	   ;lpszClassName = MonNomDeClasse
  hIconSm           DWORD      ?	   ;hIconSm = NULL
 WNDCLASSEX ENDS

Voici le détail des champs que nous modifions :

- style : le style de notre fenêtre. Positionnez à zéro.
- lpfnWndProc : il s'agit ici de passer le nom d'une fonction très utile, celle qui va gérer la réception des évènements !!! J'en présente une de base à la fin de ce chapitre.
- cbClsExtra et cbWndExtra : servent à l'allocation d'octets supplémentaires. Mettez à zéro.
- hInstance : Fournir l'instance de l'application.
- hCursor : Quel pointeur voulez-vous ? ben celui par défaut ! Cool, la fonction LoadCursor() nour le permet !
- hbrBackground : Si on ne modifie pas ce champou qu'on met (COLOR_WINDOW + 1), le fond de notre fenêtre sera blanc. Ici, on affecte la couleur système de fond de fenêtre.(COLOR_3DFACE + 1)
- lpszMenuName : Mettez zéro. On préfèrera placer un menu d'une autre manière.
- lpszClassName : Le nom que vous donnez à la classe. Préférez utiliser une variable globale de pointeur sur char, le nom de la classe sera réutiliser.
- hIcon : Nous chargerons ici une icône dans le 2ème chapitre.

Ainsi, notre WinMain() s'étoffe :

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    {
        Structure WNDCLASS wndClass	; pour initialiser la Window Class
        hInst = hInstance  		; en retour
        invoke RegisterClassEx, addr wc ; Pour enregistrer cette classe

    }

Nous avons une instance d'application et une classe de fenêtre bien définie, ce n'est pas si mal ! C'est même dejà beaucoup !!!

- 1.4: Phase de création principale -

En effet, tout va maintenant très vite ! Il nous reste à créer notre fenêtre à l'aide de CreateWindow(), qui retourne le handle de la fenêtre créée (lequel doit être récupéré dans une variable globale). Voici comment cela se passe :

  HWND hWnd;

  WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    {
        Structure WNDCLASS wndClass	; pour initialiser la Window Class
        hInst = hInstance  		; en retour
        invoke RegisterClassEx, addr wc ; Pour enregistrer cette classe

        invoke CreateWindowEx("MonNomDeClasse",	  	 	  ; Structure
                             // le nom de la classe de fenêtre	  ; CreateWindow
                        "Programmation API Win32",
                             // le titre de la fenêtre
                        WS_OVERLAPPEDWINDOW,
                             // le style de la fenêtre
                        CW_USEDEFAULT, CW_USEDEFAULT,
                             // ses positions x et y au début
                        300, 200,
                             // la taille dx et dy de la fenêtre
                        NULL,
                             // le handle de sa fenêtre parent
                        NULL,
                             // réservé au chargement d'un menu
                        hInst,
                             // l'instance de l'appli
                        NULL
                             // mettre toujours à NULL
                       );

        invoke ShowWindow, hwnd,CmdShow		; on affichee notre fenêtre
    }

Rien de bien compliqué je pense. Sinon, regardez ce que dit la doc Microsoft sur cette fonction. Pour les styles de fenêtres, ils sont innombrables. Ils se combinent à l'aide de l'opérateur OR. En général WS_OVERLAPPEDWINDOW convient dans la plupart des cas. Sinon, voici une liste peu exhaustive (le site proposera par la suite un ensemble de références plus consistant) :

- WS_BORDER : crée une fenêtre avec bordure.
- WS_CAPTION : crée une fenêtre avec barre de titre.
- WS_OVERLAPPED : combine les 2 précédents.
- WS_SYSMENU : ajoute le menu système en haut à gauche dans la barre de titre.
- WS_THICKFRAME : Possibilité de changer la taille de la fenêtre à la souris.
- WS_MINIMIZEBOX : Bouton "réduire" en haut à droite.
- WS_MINIMIZEBOX : Bouton "agrandir" en haut à droite.
- WS_OVERLAPPEDWINDOW : Combinaison de tous les styles précédents.
- WS_MAXIMIZE : La fenêtre est créée avec la taille maximum.
- et bien d'autres encore ...

Pour le dernier paramètre, sachez qu'il sert à joindre une chaine de caractère au message WM_CREATE que recevra notre fenêtre lors de sa création. Personnellement, il faudra m'expliquer l'utilité de ce système.

- 1.5: Boucle évènementielle -

C'est bien beau, mais pour l'instant, notre programme quitte directement. En fait, il ne reçoit même pas les messages de l'OS. Pour cela, nous nous servons d'une boucle classique, que nous ne modifierons jamais :

  HWND hWnd;

  WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    {
        Structure WNDCLASS wndClass	; pour initialiser la Window Class
        hInst = hInstance  		; en retour
        invoke RegisterClassEx, addr wc ; Pour enregistrer cette classe
            Strucure CreateWindowEx	   		;pour initialiser la fenêtre
        invoke ShowWindow, hwnd,CmdShow		; on affichee notre fenêtre

    // on reçoit les messages en boucle à l'aide de GetMessage()
    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0	; // msg reçoit les messages
                .BREAK .IF (!eax)  			; // Sort de la boucle si eax=-1
                invoke TranslateMessage, ADDR msg 	; // on traduit le message reçu
                invoke DispatchMessage, ADDR msg 	; // et on l'envoie vers notre
                               ; procédure de gestion de msgs
   .ENDW

   mov     eax,msg.wParam		  		; // Retoune le code de sortie dans eax

    }
ret							; // on termine la procédure
WinMain endp  						; WinMain
    // on renvoie désormais le code d'arrêt contenu dans l'objet msg

Je crois ne pas pouvoir être plus explicite. C'est de toute façon une méthode standard. Inutile de s'intéresser ici à la composition de l'objet MSG, ou au fonctionnement exacte de la réception des messages. Pour plus de compléments, référez-vous à la documentation. Pour l'heure, nous nous contenterons de prendre la boucle telle qu'elle.

- 1.6: Fonctions évènementielles -

Je vous ai parlé d'une fonction évènementielle dans l'initialisation de la classe de fenêtre sans vraiment la définir. Celle-ci repond aussi à un prototype, et permet de lancer différents traitements selon le type de message reçu. La voici incluse dans la version finale de notre programme :

Tout WinMain

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY                           ; si l'utilisateur ferme notre fenêtre (parce qu'on vient de recevoir un message WM_DESTROY)...alors
        invoke PostQuitMessage,NULL             ; on quitte notre application
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam     ; Traitement par défaut de tous les autres messages qui ne nous intéressent pas.
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

  }

Le prototype d'une fonction évènementielle est toujours le même. Les arguments reçus sont :

- • hwnd : Le handle de la fenêtre concernée par le message
- • uMsg : La référence du message
- • wParam : le 1er paramètre du message
- • lParam : le second

Pour les différents messages reçus, je n'en ferai pas la liste ici. Nous aurons l'occasion de les rencontrer tout au long de ce tutorial (WM_CLOSE, WM_CREATE, WM_SIZE, WM_MOVE, etc ...). En ce qui concerne le traitement particulier réservé à WM_DESTROY, il permet de fermer l'application à l'aide de la fonction PostQuitMessage() (qui en fait envoie le message WM_QUIT à notre fenêtre (et oui, nombre de fonctions remplacent l'utilisation de SendMessage() pour faciliter la programmation)). En effet, WM_DESTROY est un message sans traitement par défaut qui est envoyé lors d'un clik sur la croix en haut à droite, ou lors du choix "quitter" dans le menu système en haut à gauche. Il nous faut donc notifier la fermeture de l'application nous même.


VIRTUAL KEYS    Pour tout KeyBoard quelque soit la marque ou le modèle.

VK_LBUTTON    equ 01h    Left mouse button
VK_RBUTTON    equ 02h    Right mouse button
VK_CANCEL     equ 03h    Ctrl/Break                Control- break processing
VK_MBUTTON    equ 04h    Bouton du milieu          Middle mouse button or left and right button
                         05h 06h 07h  ?
VK_BACK       equ 08h    espace arriére            Blackspace key
VK_TAB        equ 9h     Tabulation                Tab
                         0Ah 0Bh  ?
VK_CLEAR      equ 0Ch    5 sur pavé numérique      clear
VK_RETURN     equ 0Dh    Enter                     return
                         0Eh 0Fh
VK_SHIFT      equ 10h    Majuscule                 shift
VK_CONTROL    equ 11h    CTRL contrôle             ctrl
VK_MENU       equ 12h    Alt                       alt
VK_PAUSE      equ 13h    Pause                     pause
VK_CAPITAL    equ 14h    Vérrouillage majuscule    capslock

VK_KANA       equ 15h
VK_HANGEUL    equ 15h
VK_HANGUL     equ 15h
VK_JUNJA      equ 17h
VK_FINAL      equ 18h
VK_HANJA      equ 19h
VK_KANJI      equ 19h
                         1Ah ?
VK_ESCAPE     equ 1Bh    Echappement               esc
VK_CONVERT    equ 1C
VK_NONCONVERT equ 1D
VK_ACCEPT     equ 1E
VK_MODECHANGE equ 1F
VK_SPACE      equ 20h    Espace                    spacebar

VK_PGUP       equ 21h    Page UP                   pgup
VK_PRIOR      equ 21h    précédent                 pgup
VK_PGDN       equ 22h    Page DOWN                 pgdn
VK_NEXT       equ 22h    prochain                  pgdn

VK_END        equ 23h    fin                       end
VK_HOME       equ 24h    Début                     home
VK_LEFT       equ 25h        gauche
VK_UP         equ 26h    Fléche haut
VK_RIGHT      equ 27h        droite
VK_DOWN       equ 28h        bas
VK_SELECT     equ 29h                              select
VK_PRINT      equ 2Ah
VK_EXECUTE    equ 2Bh                              execute
VK_SNAPSHOT   equ 2Ch                              printscreen
VK_INSERT     equ 2Dh    Insert
VK_DELETE     equ 2Eh    Suppression  (Suppr)      del
VK_HELP       equ 2Fh                              help

VK_0          equ 30h    VK_0 thru VK_9 are the same as ASCII '0' thru '9' (equ 30h equ 39h) ... 30-39  0 à 9 avec VK_x equ ASCII
                ----
VK_A          equ 41h    VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (equ 41h equ 5Ah) ... 41-5A  A à Z avec VK_x equ ASCII
                ----

VK_LWIN       equ 5Bh
VK_RWIN       equ 5Ch
VK_APPS       equ 5Dh
                         5Eh  à  5Fh ?
VK_NUMPAD0    equ 60h    Munérique keyboard
VK_NUMPAD1    equ 61h        -
VK_NUMPAD2    equ 62h        -
VK_NUMPAD3    equ 63h        -
VK_NUMPAD4    equ 64h        -
VK_NUMPAD5    equ 65h        -
VK_NUMPAD6    equ 66h        -
VK_NUMPAD7    equ 67h        -
VK_NUMPAD8    equ 68h        -
VK_NUMPAD9    equ 69h        -
VK_MULTIPLY   equ 6Ah    Multiply        106       multiply
VK_ADD        equ 6Bh    ADD             107       add
VK_SEPARATOR  equ 6Ch                              separator
VK_SUBTRACT   equ 6Dh    Subtract        109       subtract
VK_DECIMAL    equ 6Eh    Decimal         110       decimal
VK_DIVIDE     equ 6Fh    Divide          111       divide
VK_F1         equ 70h    F1-F24 keys
VK_F2         equ 71h
VK_F3         equ 72h
VK_F4         equ 73h
VK_F5         equ 74h
VK_F6         equ 75h
VK_F7         equ 76h
VK_F8         equ 77h
VK_F9         equ 78h
VK_F10        equ 79h
VK_F11        equ 7Ah
VK_F12        equ 7Bh
VK_F13        equ 7Ch
VK_F14        equ 7Dh
VK_F15        equ 7Eh
VK_F16        equ 7Fh
VK_F17        equ 80h
VK_F18        equ 81h
VK_F19        equ 82h
VK_F20        equ 83h
VK_F21        equ 84h
VK_F22        equ 85h
VK_F23        equ 86h
VK_F24        equ 87h
                          88h à 8Fh ?
VK_NUMLOCK    equ 90h          NUMLOCK key
VK_SCROLL     equ 91h       SCROLLLOCK key
VK_LSHIFT     equ 0A0h    Left   SHIFT key   Only  GetKeyState  and  GetAsyncKeyState
VK_RSHIFT     equ 0A1h    Right  SHIFT key              -                   -
VK_LCONTROL   equ 0A2h    Left    CTRL key              -                   -
VK_RCONTROL   equ 0A3h    Right   CTRL key              -                   -
VK_LMENU      equ 0A4h    Left     ALT key              -                   -
VK_RMENU      equ 0A5h    Right    ALT key              -                   -
VK_PROCESSKEY equ E5h
VK_ATTN       equ F6h
VK_CRSEL      equ F7h
VK_EXSEL      equ F8h
VK_EREOF      equ F9h
VK_PLAY       equ FAh
VK_ZOOM       equ FBh
VK_NONAME     equ FCh
VK_PA1        equ FDh
VK_OEM_CLEAR  equ FEh     FFh

VK_0          equ 30h
VK_1          equ 31h
VK_2          equ 32h
VK_3          equ 33h
VK_4          equ 34h
VK_5          equ 35h
VK_6          equ 36h
VK_7          equ 37h
VK_8          equ 38h
VK_9          equ 39h

VK_A          equ 41h
VK_B          equ 42h
VK_C          equ 43h
VK_D          equ 44h
VK_E          equ 45h
VK_F          equ 46h
VK_G          equ 47h
VK_H          equ 48h
VK_I          equ 49h
VK_J          equ 4Ah
VK_K          equ 4Bh
VK_L          equ 4Ch
VK_M          equ 4Dh
VK_N          equ 4Eh
VK_O          equ 4Fh
VK_P          equ 50h
VK_Q          equ 51h
VK_R          equ 52h
VK_S          equ 53h
VK_T          equ 54h
VK_U          equ 55h
VK_V          equ 56h
VK_W          equ 57h
VK_X          equ 58h
VK_Y          equ 59h
VK_Z          equ 5Ah


Exemple

+$#KK Timers (Vu dans Win32 HLP 11 279 097 Octets)
A timer is an internal routine that repeatedly measures a specified interval, in milliseconds. Each time the interval (or time-out value) elapses, the system notifies the window associated with the timer. Because the accuracy of a timer depends on the system clock rate and how often the application retrieves messages from the message queue, the time-out value is only approximate.

+$#K About Timers
An application written for Microsoft® Windows® creates a timer by using the SetTimer function. If a window handle is given in this function, the application creates the timer for that window. Then, whenever the time-out value elapses, the system posts a WM_TIMER message to the window. If no window handle is given in SetTimer, the application that created the timer must monitor its message queue for WM_TIMER messages and dispatch them to the appropriate window.
A new timer starts timing as soon as it is created. An application can change a timer's time-out value by using SetTimer and can destroy a timer by using the KillTimer function. To use system resources efficiently, applications should destroy timers that are no longer necessary.
Each timer has a unique identifier. When creating a timer, an application can either specify an identifier or have the system create a unique value. The first parameter of a WM_TIMER message contains the identifier of the timer that posted the message.

+$#K Using Timers
· Creating a timer
· Destroying a timer
· Using timer functions to create a mousetrap
· Process the WM_TIMER message.
· Use a TimerProc callback function.

+$#K Creating a Timer
The following example uses the SetTimer function to create two timers. The first timer is set for every 10 seconds, the second for every 5 minutes.
/* Set two timers. */

SetTimer(hwnd, /* handle of main window */
IDT_TIMER1, /* timer identifier */
10000, /* 10-second interval */
(TIMERPROC) NULL); /* no timer callback */

SetTimer(hwnd, /* handle of main window */
IDT_TIMER2, /* timer identifier */
300000, /* 5-minute interval */
(TIMERPROC) NULL); /* no timer callback */

To process the WM_TIMER messages generated by these timers, add a WM_TIMER case statement to the window procedure for the hwnd parameter.
case WM_TIMER:

switch (wParam) {

case IDT_TIMER1:
.
. /* Process the 10-second timer. */
.
return 0;

case IDT_TIMER2:
.
. /* Process the 5-minute timer. */
.
return 0;
}

An application can also create a timer whose WM_TIMER messages are processed not by the main window procedure but by an application-defined callback function, as in the following code sample, which creates a timer and uses the callback function MyTimerProc to process the timer's WM_TIMER messages.
/* Set the timer. */

SetTimer(hwnd, /* handle of main window */
IDT_TIMER3, /* timer identifier */
5000, /* 5-second interval */
(TIMERPROC) MyTimerProc); /* timer callback */

The calling convention for MyTimerProc must be based on the Microsoft® Win32® callback function, TimerProc.
If your application creates a timer without specifying a window handle, your application must monitor the message queue for WM_TIMER messages and dispatch them to the appropriate window.
HWND hwndTimer; /* handle of window for timer messages */
MSG msg; /* message structure */

while (GetMessage(&msg, /* message structure */
NULL, /* handle of window receiving msg. */
NULL, /* lowest message to examine */
NULL)) /* highest message to examine */
{

/* Post WM_TIMER messages to the hwndTimer procedure. */

if (msg.message == WM_TIMER) {
msg.hwnd = hwndTimer;
}
TranslateMessage(&msg); /* translates virtual-key codes */
DispatchMessage(&msg); /* dispatches message to window */
}

+$#K Destroying a Timer
Applications should use the KillTimer function to destroy timers that are no longer necessary. The following example destroys the timers identified by the constants IDT_TIMER1, IDT_TIMER2, and IDT_TIMER3.
/* Destroy the timers. */

KillTimer(hwnd, IDT_TIMER1);
KillTimer(hwnd, IDT_TIMER2);
KillTimer(hwnd, IDT_TIMER3);

+$#K Using Timer Functions to Create a Mousetrap
Sometimes it is necessary to prevent more input while you have a cursor on the screen. One way to accomplish this is to create a special routine that traps mouse input until a specific event occurs. Many developers refer to this routine as "building a mousetrap."
The following example uses the SetTimer and KillTimer functions to create a simple mousetrap. SetTimer creates a timer that sends a WM_TIMER message every 10 seconds. Each time the application receives a WM_TIMER message, it records the cursor location. If the current location is the same as the previous location and the application's main window is minimized, the application moves the cursor to the icon. When the application closes, KillTimer stops the timer.
HICON hIcon1; /* icon handle */
POINT ptOld; /* previous cursor location */
UINT uResult; /* SetTimer's return value */
HINSTANCE hinstance; /* handle of current instance */
.
. /* Perform application initialization here. */

wc.hIcon = LoadIcon(hinstance, MAKEINTRESOURCE(400));
wc.hCursor = LoadCursor(hinstance, MAKEINTRESOURCE(200));

/* Record the initial cursor position. */

GetCursorPos(&ptOld);

/* Set the timer for the mousetrap. */

uResult = SetTimer(hwnd, /* handle of main window */
IDT_MOUSETRAP, /* timer identifier */
10000, /* 10-second interval */
(TIMERPROC) NULL); /* no timer callback */

if (uResult == 0) {
ErrorHandler("No timer is available.");
}

LONG APIENTRY MainWndProc(
HWND hwnd, /* handle of main window */
UINT message, /* type of message */
UINT wParam, /* additional information */
LONG lParam) /* additional information */
{

HDC hdc; /* handle of device context */
POINT pt; /* current cursor location */
RECT rc; /* location of minimized window */

switch (message) {
.
. /* Process other messages. */
.
case WM_TIMER:
/*
* If the window is minimized, compare the current
* cursor position with the one from 10 seconds
* earlier. If the cursor position has not changed,
* move the cursor to the icon.
*/

if (IsIconic(hwnd)) {
GetCursorPos(&pt);

if ((pt.x == ptOld.x) && (pt.y == ptOld.y)) {
GetWindowRect(hwnd, &rc);
SetCursorPos(rc.left, rc.top);
}
else {
ptOld.x = pt.x;
ptOld.y = pt.y;
}
}
return 0;
case WM_DESTROY:
/* Destroy the timer. */

KillTimer(hwnd, IDT_MOUSETRAP);
PostQuitMessage(0);
break;
.
. /* Process other messages. */
.
}
Although the following example also creates a mousetrap, it processes the WM_TIMER message through the application-defined callback function MyTimerProc, rather than through the application's message queue.
UINT uResult; /* SetTimer's return value */
HICON hIcon1; /* icon handle */
POINT ptOld; /* previous cursor location */
HINSTANCE hinstance; /* handle of current instance */
.
. /* Perform application initialization here. */
.
wc.hIcon = LoadIcon(hinstance, MAKEINTRESOURCE(400));
wc.hCursor = LoadCursor(hinstance, MAKEINTRESOURCE(200));

/* Record the current cursor position. */

GetCursorPos(&ptOld);

/* Set the timer for the mousetrap. */

uResult = SetTimer(hwnd, /* handle of main window */
IDT_MOUSETRAP, /* timer identifier */
10000, /* 10-second interval */
(TIMERPROC) MyTimerProc); /* timer callback */

if (uResult == 0) {
ErrorHandler("No timer is available.");
}
LONG APIENTRY MainWndProc(
HWND hwnd, /* handle of main window */
UINT message, /* type of message */
UINT wParam, /* additional information */
LONG lParam) /* additional information */
{

HDC hdc; /* handle of device context */

switch (message) {
.
. /* Process other messages. */
.
case WM_DESTROY:

/* Destroy the timer. */

KillTimer(hwnd, IDT_MOUSETRAP);
PostQuitMessage(0);
break;
.
. /* Process other messages. */
.
}

/*
* MyTimerProc is an application-defined callback function that
* processes WM_TIMER messages.
*/

VOID CALLBACK MyTimerProc(
HWND hwnd, /* handle of window for timer messages */
UINT message, /* WM_TIMER message */
UINT idTimer, /* timer identifier */
DWORD dwTime) /* current system time */
{

RECT rc;
POINT pt;

/*
* If the window is minimized, compare the current
* cursor position with the one from 10 seconds earlier.
* If the cursor position has not changed, move the
* cursor to the icon.
*/

if (IsIconic(hwnd)) {
GetCursorPos(&pt);

if ((pt.x == ptOld.x) && (pt.y == ptOld.y)) {
GetWindowRect(hwnd, &rc);
SetCursorPos(rc.left, rc.top);
}
else {
ptOld.x = pt.x;
ptOld.y = pt.y;
}
}
}

+$#K Timer Functions
The following functions are used with timers.
KillTimer
SetTimer
TimerProc


ASM Mode Reel

             List of routines req'd after a Win Resource is used

             Resource        Allocation Routine        Cleanup Routine      Cleanup Required?
             ================================================================================

             Accelerators    LoadAccelerators          N/A                  No

             Atoms           GlobalAddAtom             GlobalFreeAtom       Yes

             Bitmaps         CreateBitmap              DeleteObject         Yes *
                             CreateBitmapIndirect      DeleteObject         Yes *
                             CreateCompatibleBitmap    DeleteObject         Yes *
                             CreateDIBitmap            DeleteObject         Yes *
                             CreateDiscardableBitmap   DeleteObject         Yes *
                             LoadBitmap                DeleteObject         Yes *

             Brushes         CreateBrushIndirect       DeleteObject         Yes
                             CreateDIBPatternBrush     DeleteObject         Yes
                             CreateHatchBrush          DeleteObject         Yes
                             CreatePatternBrush        DeleteObject         Yes
                             CreateSolidBrush          DeleteObject         Yes

             Carets          CreateCaret               DestroyCaret         No  #

             Clipboard       OpenClipboard             CloseClipboard       Yes

             Comms Ports     OpenComm                  CloseComm            Yes

             Cursor          CreateCursor              DestroyCursor        Yes
                             LoadCursor                N/A                  No

             DC              CreateDC                  DeleteDC             Yes
                             CreateCompatibleDC        DeleteDC             Yes @
                             GetDC                     ReleaseDC            Yes
                             GetWindowDC               ReleaseDC            Yes
                             BeginPaint                EndPaint             Yes

             Dialog Box      CreateDialog              DestroyWindow        No
                             CreateDialogIndirect      DestroyWindow        No
                             CreateDialogIndirectParam DestroyWindow        No
                             CreateDialogParam         DestroyWindow        No

             File            OpenFile                  _lclose              No

             Fonts           CreateFont                DeleteObject         Yes
                             CreateFontIndirect        DeleteObject         Yes
                             AddFontResource           RemoveFontResource   Yes

             GDI Stock Obj   GetStockObject            N/A                  No  &

             Hook            SetWindowsHook            UnhookWindowsHook    Yes

             IC              CreateIC                  DeleteDC             Yes

             Icon            CreateIcon                DestroyIcon          Yes
                             LoadIcon                  N/A                  No

             Library         LoadLibrary               FreeLibrary          Yes

             Memory          AllocDStoCSAlias          FreeSelector         Yes
                             AllocSelector             FreeSelector         Yes
                             GlobalAlloc               GlobalFree           No
                             GlobalDOSAlloc            GlobalDOSFr          No
                             GlobalFix                 GlobalUnfix          No
                             GlobalLock                GlobalUnlock         No
                             GlobalPageLock            GlobalPageUnlock     No
                             GlobalRealloc             GlobalFree           No
                             GlobalWire                GlobalUnwire         No
                             LocalAlloc                LocalFree            No
                             LocalLock                 LocalUnlock          No
                             LocalRealloc              LocalFree            No

             Menu            CreateMenu                DestroyMenu          No  %
                             CreatePopupMenu           DestroyMenu          No  %
                             LoadMenu                  DestroyMenu          No  %
                             LoadMenuIndirect          DestroyMenu          No  %
                             GetSystemMenu             N/A                  No

             MetaFile        CreateMetaFile            DeleteMetaFile       Yes

             Palette         CreatePalette             DeleteObject         Yes

             Pen             CreatePen                 DeleteObject         Yes
                             CreatePenIndirect         DeleteObject         Yes

             Regions         CreateEllipticRgn         DeleteObject         Yes
                             CreateEllipticRgnIndirect DeleteObject         Yes
                             CreatePolygonRgn          DeleteObject         Yes
                             CreatePolyPolygonRgn      DeleteObject         Yes
                             CreateRectRgn             DeleteObject         Yes
                             CreateRectRgnIndirect     DeleteObject         Yes
                             CreateRoundRectRgn        DeleteObject         Yes

             Resource        AllocResource             FreeResource         No
                             FindResource              N/A                  No
                             LoadResource              FreeResource         No
                             LockResource              UnlockResource       No

             Sound           OpenSound                 CloseSound           Yes

             String          LoadString                N/A                  No

             Thunk           MakeProcInstance          FreeProcInstance     No

             Timer           SetTimer                  KillTimer            No

             Window          CreateWindow              DestroyWindow        No
                             CreateWindowEx            DestroyWindow        No

             Notes:
             ------
             *   Bitmaps must be disconnected from a DC before being freed.
             %   Menus must be freed before an application exits if the menu is not
                 connected to any window.
             @   A DC must be disconnected from bitmaps before being freed.
             #   If a bitmap is specified for the Caret, it must be freed.
             &   Calls to DeleteObject do not harm stock objects.


Java et Eclipse tutoriel JM_Doudoux

Programmation avec l'API Windows

Télécharger le projet ... http://bob.developpez.com/tutapiwin/projets/
  • Les bases d'un programme Windows
    • I. Les bases d'un programme Windows
      • I-A. Création d'une boîte de dialogue
        • I-A-1. Cours théorique
        • I-A-2. Projet N°1
      • I-B. Création d'une fenêtre
        • I-B-1. Cours théorique
        • I-B-2. Projet N°2
      • I-C. Affichage dans une fenêtre
        • I-C-1. Cours théorique
        • I-C-2. Projet N°3
      • I-D. Lecture dans un fichier et boîte de dialogue
        • I-D-1. Cours théorique
        • I-D-2. Projet N°4
      • I-E. Pas à pas pour C++ Builder
        • I-E-1. BCB 6
        • I-E-2. BCB 4
      • I-F. Lecture d'un fichier WAV
        • I-F-1. Cours théorique
        • I-F-2. Projet N°5
      • I-G. Création d'une application console
        • I-G-1. Cours théorique
      • I-H. Fenêtre classique ou ressource ?
        • I-H-1. Cours théorique
  • Les boîtes de dialogue
    • II. Les boîtes de dialogue
      • II-A. Fonctionnement général
        • II-A-1. Cours théorique
      • II-B. Initialisation
        • II-B-1. Cours théorique
      • II-C. Le contrôle 'Edit'
        • II-C-1. Cours théorique
      • II-D. Le contrôle 'Check Box'
        • II-D-1. Cours théorique
      • II-E. Le contrôle 'Radio Button'
        • II-E-1. Cours théorique
      • II-F. Le contrôle 'List Box'
        • II-F-1. Cours théorique
      • II-G. Le contrôle 'Progress bar'
        • II-G-1. Cours théorique
      • II-H. Le contrôle 'Combo box'
        • II-H-1. Cours théorique
      • II-I. Synthèse sur les contrôles
        • II-I-1. Cours théorique
      • II-J. Deux projets récapitulatifs
        • II-J-1. Cours théorique
        • II-J-2. Projet N°6a
      • II-K. MessageBox()
        • II-K-1. Cours théorique
      • II-L. Parcourir l'arborescence
        • II-L-1. Cours théorique
  • Les fenêtres
    • III. Les fenêtres
      • III-A. Fenêtres et boîtes de dialogue
        • III-A-1. Cours théorique
      • III-B. Fonctionnement général d'une fenêtre
        • III-B-1. Cours théorique
      • III-C. Récupération des messages
        • III-C-1. Cours théorique
      • III-D. Création d'une fenêtre
        • III-D-1. Cours théorique
      • III-E. Destruction d'une fenêtre
        • III-E-1. Cours théorique
      • III-F. Contexte d'affichage
        • III-F-1. Cours théorique
      • III-G. Gestion de base d'une fenêtre
        • III-G-1. Cours théorique
      • III-H. Interactions avec l'utilisateur
        • III-H-1. Cours théorique
      • III-I. Les timers
        • III-I-1. Cours théorique
      • III-J. Un projet récapitulatif
        • III-J-1. Cours théorique
      • III-K. Affichage de texte dans une fenêtre
        • III-K-1. Cours théorique
      • III-L. Utilisation de polices personnalisées
        • III-L-1. Cours théorique
      • III-M. Affichage d'une image
        • III-M-1. Cours théorique
      • III-N. Dessin
        • III-N-1. Cours théorique
      • III-O. Fond personnalisé
        • III-O-1. Cours théorique
      • III-P. Commandes systèmes
        • III-P-1. Cours théorique
      • III-Q. Menus
        • III-Q-1. Cours théorique
      • III-R. Création dynamique d'un contrôle
        • III-R-1. Cours théorique
  • Le système de fichier
    • IV. Le système de fichier
      • IV-A. Introduction
        • IV-A-1. Cours théorique
      • IV-B. Création et ouverture d'un fichier
        • IV-B-1. Cours théorique
      • IV-C. Lecture/Écriture dans un fichier
        • IV-C-1. Cours théorique
      • IV-D. Manipulations sur les fichiers
        • IV-D-1. Cours théorique
      • IV-E. Enumération de fichiers
        • IV-E-1. Cours théorique
      • IV-F. Manipulations sur les dossiers
        • IV-F-1. Cours théorique
      • IV-G. Manipulations des chemins
        • IV-G-1. Cours théorique
  • Le multithreading
    • V. Le multithreading
      • V-A. Introduction
        • V-A-1. Cours théorique
      • V-B. Notion de processus et de thread
        • V-B-1. Cours théorique
      • V-C. Partage des tâches
        • V-C-1. Cours théorique
      • V-D. Synchronisation
        • V-D-1. Cours théorique
      • V-E. Premier exemple
        • V-E-1. Cours théorique
      • V-F. Arrêt d'un thread
        • V-F-1. Cours théorique
      • V-G. Récupération des messages
        • V-G-1. Cours théorique
      • V-H. Communication inter-threads
        • V-H-1. Cours théorique
      • V-I. Sections Critiques
        • V-I-1. Cours théorique
      • V-J. Fonctions d'attente
        • V-J-1. Cours théorique
      • V-K. Événements
        • V-K-1. Cours théorique
      • V-L. Sémaphores
        • V-L-1. Cours théorique
      • V-M. Trois projets simples
        • V-M-1. Cours théorique
      • V-N. Performances
        • V-N-1. Cours théorique
  • Annexes
    • VI. Annexes
      • VI-A. Annexe A : Les contrôles
        • VI-A-1. Styles des contrôles
        • VI-A-2. Messages utilisables avec les contrôles
      • VI-B. Annexe B : Fonctions par thèmes


Java et Eclipse tutoriel JM_Doudoux

Programmation avec l'API Windows

Télécharger le projet ... http://bob.developpez.com/tutapiwin/projets/
  • I. Les bases d'un programme Windows
    • I-A. Création d'une boîte de dialogue
      • I-A-1. Cours théorique
      • I-A-2. Projet N°1
    • I-B. Création d'une fenêtre
      • I-B-1. Cours théorique
      • I-B-2. Projet N°2
    • I-C. Affichage dans une fenêtre
      • I-C-1. Cours théorique
      • I-C-2. Projet N°3
    • I-D. Lecture dans un fichier et boîte de dialogue
      • I-D-1. Cours théorique
      • I-D-2. Projet N°4
    • I-E. Pas à pas pour C++ Builder
      • I-E-1. BCB 6
      • I-E-2. BCB 4
    • I-F. Lecture d'un fichier WAV
      • I-F-1. Cours théorique
      • I-F-2. Projet N°5
    • I-G. Création d'une application console
      • I-G-1. Cours théorique
    • I-H. Fenêtre classique ou ressource ?
      • I-H-1. Cours théorique

I. Les bases d'un programme Windows

I-A. Création d'une boîte de dialogue

I-A-1. Cours théorique

Bien comprendre le principe de fonctionnement d'une application Windows est essentiel avant de commencer à programmer. Je vais ici exposer le principe global de fonctionnement d'une application créant une fenêtre.

Tout d'abord, il convient de bien garder à l'esprit qu'un programme fonctionnant sous Windows et gérant une fenêtre doit rester en dialogue permanent avec Windows. Le programme ne connaît (a priori) rien sur son environnement. C'est Windows qui signale à l'application si elle doit redessiner le contenu d'une de ses fenêtres, ou encore si l'utilisateur essaie de fermer la fenêtre.

Cette communication se fait au travers de messages que Windows envoie à chaque fenêtre concernée. C'est au programme d'effectuer la réception de ces messages et de les transmettre aux fonctions gérant les différentes fenêtres. La réception de ces messages ne doit donc pas prendre de retard, et le traitement de chacun des messages doit être bref. En cas de retard, le redessinement des fenêtres n'est donc plus assuré, ce qui a pour conséquence des fenêtres blanches, non déplaçable, similaires à celles des programmes "plantés".

Chaque fenêtre est associée à une fonction ou procédure de fenêtre (Window Proc). Parfois plusieurs fenêtres peuvent être associées à une même procédure. Chaque message reçu est transmis à la procédure correspondante qui se chargera de traiter ce message (redessiner la fenêtre, la redimensionner, afficher un caractère entré par l'utilisateur…).

Une partie du travail de rafraîchissement de la fenêtre est pris en charge par Windows. Le programme n'a à redessiner que la zone client de sa fenêtre (et non pas la barre de titre, les menus éventuels…).

I-A-2. Projet N°1

Objectif : créer une boîte de dialogue et une procédure très simple chargée de la gérer.

Réalisation : la boîte de dialogue sera créée grâce à l'éditeur de ressources de VC++. Le programme sera chargé de la réception des messages et du traitement des plus simples.

Télécharger le projet.

Le projet pas à pas :

Tout d'abord, il s'agit de créer le projet dans VC++ : File/New/Projects/Win32 Application. Donnez ensuite un nom à votre projet et cliquez 'Ok'. Ensuite, sélectionnez 'An empty project' puis 'Finish' et 'Ok'. Nous venons de créer un projet vide, il ne contient aucun fichier de code.

Ajoutons maintenant un fichier de code et fichier de ressources. Allez dans File/New/Files/C++ Source File. Appelez ce fichier main.cpp et validez. Refaites la même opération mais avec un fichier de type Ressource Script nommé res. Fermez le fichier qui s'est ouvert dans l'éditeur. Le script de ressources est associé à un fichier d'en-tête créé et mis à jour automatiquement, mais qu'il convient d'ajouter. Dans le feuillet FileView du panneau de gauche, faites un clic droit sur Header Files et sélectionnez Add Files to Folder. Ajoutez le fichier ressource.h qui doit être dans le même répertoire que votre projet.

Créons à présent le modèle de notre boîte de dialogue. Allez dans le feuillet RessourceView du panneau de travail (à gauche). Faites un clic droit sur res ressources et sélectionnez Insert/Dialog/New. Nous ne modifierons pas cette boîte de dialogue.

Occupons-nous maintenant du code qui va constituer le programme. Tout d'abord, les fichiers d'en-tête, deux sont nécessaires, Windows.h et ressource.h que nous avons inclus précédemment.

Le point d'entrée du programme est la fonction WinMain(). Elle est exécutée automatiquement par Windows lorsque le programme est démarré. Mais avant cette fonction, nous aurons la définition de la procédure de notre fenêtre principale, MainPoc().

Sélectionnez
#include <Windows.h>
#include "ressource.h"

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{

Nous allons maintenant créer la boîte de dialogue grâce à la fonction CreateDialog(). Le paramètre HINSTANCE est un identifiant de notre application, on passe l'identifiant fourni par Windows en paramètre de la fonction WinMain(). On donne ensuite l'identifiant ressource de notre boîte de dialogue. Elle n'a pas de fenêtre parent et la procédure est la fonction MainProc(). La fonction retourne une variable de type HWND qui identifie notre fenêtre. Puis, on affiche cette fenêtre. Toute fenêtre créée est invisible par défaut.

Sélectionnez
   HWND hDlg;
   hDlg=CreateDialog(hInstance,(LPCTSTR)IDD_DIALOG1,NULL,(DLGPROC)MainProc);
   ShowWindow(hDlg,SW_SHOW);

Occupons-nous maintenant de la réception des messages. La réception des messages est prise en charge par la fonction GetMessage(). Cette fonction retourne FALSE dès que le message WM_QUIT est reçu, ce qui indique que l'application doit être fermée.

Sélectionnez
   MSG msg;
   while(GetMessage(&msg,NULL,0,0)==TRUE)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return 0;
}

Il ne reste plus qu'à définir le comportement de notre fenêtre dans quelques cas simples: elle doit être fermée et l'application quittée si l'utilisateur presse 'ok' ou essaie de quitter. La fermeture de la fenêtre ne signifie pas forcément l'arrêt de l'application. Ces deux comportements correspondent à deux messages distincts: WM_QUIT indique que l'application doit se terminer, WM_CLOSE indique la fermeture de la fenêtre. Mais dans ce cas, on a une fenêtre un peu particulière, puisqu'il s'agit d'une boîte de dialogue. Sa procédure est définie de la même manière que celle d'une fenêtre.

Sélectionnez
LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDOK:
         EndDialog(Dlg,0);
         PostQuitMessage(0);
         return TRUE;
      case IDCANCEL:
         EndDialog(Dlg,Select);
         PostQuitMessage(0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

Cette procédure est très simple, elle ne réagit qu'à la pression des boutons 'Ok' et 'Cancel' (le bouton ou la "croix"). En réponse à l'un ou l'autre de ces événements, la procédure demande la fermeture de la boîte de dialogue et termine l'application en envoyant un message WM_QUIT grâce à la fonction PostQuitMessage().

I-B. Création d'une fenêtre

I-B-1. Cours théorique

Dans la première partie de ce chapitre, nous avons vu comment créer une boite de dialogue. La création d'une boite de dialogue grâce à la fonction CreateDialog() est relativement simple puisqu'une grande partie du travail est effectué par CreateDialog().

Nous allons donc voir maintenant comment créer une fenêtre. Cette fenêtre pourra être personnalisée et ne se réferra à aucune ressource. Notre programme n'utilisera donc pas de ressources.

Comme nous avons pu le voir dans la partie précédente, le programme n'intervient en aucun cas dans le dessin de la fenêtre (barre de titre, menu...). Le programme n'est responsable que du redessinement de la zone client de la fenêtre. Comme il existe plusieurs types de fenêtre (avec un menu, sans menu, pouvant être minimisée ou pas...) il est nécessaire que de créer un 'protoype de fenêtre'. Ce prototype est aussi appelé classe de la fenêtre. C'est sur ce prototype que Windows se basera pour créer la fenêtre.

Une fois le prototype de fenêtre créé, nous demanderons à Windows de créer une fenêtre suivant ce prototype. La fenêtre créée devra elle aussi disposer d'une procédure permettant de traiter les messages qu'elle reçoit. Cette procédure est très similaire à celle gérant la boite de dialogue de la partie précédente. Elle ne traite cependant pas exactement les messages de la même manière.

I-B-2. Projet N°2

Objectif : créer une fenêtre sans utiliser de ressource et une procédure permettant sa gestion.

Réalisation : la fenêtre sera créée sans utiliser de ressource et sa procédure traitera les messages les plus simples.

Télécharger le projet.

Le projet pas à pas :

Il s'agit tout d'abord de créer un projet dans Microsoft Visual C++. Le projet sera créé de la même manière que dans la partie précédente, mais sans y ajouter de Ressource Script.

Le point d'entrée WinMain() est un standard à toute application Windows, ce programme débutera donc de la même manière que le précédent.

Sélectionnez
#include <Windows.h>

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{

Il faut maintenant créer une nouvelle classe de fenêtre. Cette classe est créée au moyen de la fonction RegisterClassEx(). La création de cette classe peut paraître fastidieuse et comprend un grand nombre de paramètres. nous n'utiliserons pas tous les paramètres, donc ne vous effrayez pas si vous ne comprenez pas totalement l'utilité de chacun d'eux.

Sélectionnez
   WNDCLASSEX principale;
   principale.cbSize=sizeof(WNDCLASSEX);
   principale.style=CS_HREDRAW|CS_VREDRAW;
   principale.lpfnWndProc=MainProc;
   principale.cbClsExtra=0;
   principale.cbWndExtra=0;
   principale.hInstance=hInstance;
   principale.hIcon=LoadIcon(NULL,IDI_APPLICATION);
   principale.hCursor=LoadCursor(NULL,IDC_ARROW);
   principale.hbrBackground=reinterpret_cast<HBRUSH>(COLOR_WINDOW+1);
   principale.lpszMenuName=NULL;
   principale.lpszClassName="std";
   principale.hIconSm=LoadIcon(NULL,IDI_APPLICATION);
   RegisterClassEx(&principale);

principale.style=CS_HREDRAW|CS_VREDRAW indique que notre fenêtre devra être redessinée en cas de redimensionnement horizontal ou vertical. Nous indiquons ensuite quelle procédure sera chargée de gérer la fenêtre. L'instance de notre application est elle aussi passée en paramètre. Les curseurs ou icônes de notre fenêtre sont ceux par défaut de Windows. La fenêtre n'as pas de menu, la couleur de fond est celle de Windows par défaut. Le nom de la classe de la fenêtre 'std' est un choix personnel. Il n'a d'importance que pour nous. Une fois la structure remplie, un appel à RegisterClassEx() demande à Windows de mémoriser la classe.

Maintenant que nous disposons de notre classe de fenêtre, il ne reste plus qu'a la créer grâce à la fonction CreateWindowEx().

Sélectionnez
   HWND hWnd;
   hWnd=CreateWindowEx(
      WS_EX_CLIENTEDGE,
      "std",
      "Notre fenêtre",
      WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      NULL,
      NULL,
      hInstance,
      NULL
   );

   ShowWindow(hWnd,SW_SHOW);

La réception des messages se fait exactement de la même manière que dans le projet précédent.

Sélectionnez
   MSG msg;
   while(GetMessage(&msg,NULL,0,0)==TRUE)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return 0;
}

La définition de la procédure de cette fenêtre est la même que dans le programme précédent. Mais son fonctionnement interne est différent.

Tout d'abord, cette fenêtre ne contient pas de bouton. Sa fermeture se fera lors d'un clic sur la 'croix'. Les messages non traités sont passés à DefWindowProc(). Les messages traités seront WM_PAINT et WM_DESTROY. WM_PAINT indique que nous devons redessiner la zone client. Ici le traitement de ce message ne fera rien et pourrait être ignoré. Il est inclus dans le seul but de comprendre le mécanisme de redessinement d'une fenêtre. Le message WM_DESTROY indique que la fenêtre est en train d'être détruite (probablement suite au clic sur la 'croix'). Le fermeture de la fenêtre ne signifie pas forcément l'arrêt de l'application. Mais comme dans ce cas notre application n'est formée que d'une fenêtre, nous demanderons à terminer l'application dans ce cas en postant un message WM_QUIT.

Sélectionnez
LRESULT CALLBACK MainProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam)
{
   HDC hDC;
   PAINTSTRUCT paintst;
   switch (mes)
   {
   case WM_PAINT:
      hDC=BeginPaint(hWnd,&paintst);
      EndPaint(hWnd,&paintst);
      return 0;
   case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
   default:
      return DefWindowProc(hWnd, mes, wParam, lParam);
   }
}

Le traitement du message WM_PAINT peut vous paraître étrange. En effet, pourquoi effectuer un BeginPaint() et un EndPaint() alors que nous ne dessinons rien. La raison est très simple. À partir du moment ou nous décidons de traiter ce message, nous devons assurer un traitement minimum par la suite de ces deux fonctions, même si rien n'est dessiné.

I-C. Affichage dans une fenêtre

I-C-1. Cours théorique

Maintenant que nous savons créer une fenêtre, nous allons voir comment dessiner dans sa zone client. L'affichage dans la zone client d'une fenêtre se fait au niveau du message WM_PAINT. Pour provoquer l'envoi d'un tel message, il faut demander le redessinement d'une zone de la fenêtre. Lors du dessin dans la partie WM_PAINT, il sera impossible d'afficher hors de la zone de redessinement.

Pour dessiner dans une fenêtre, nous allons utiliser un contexte d'affichage. Il est propre à la fenêtre dans laquelle nous dessinons et nous est fourni par Windows. Il identifie en fait la zone de l'écran dans laquelle nous allons dessiner.

I-C-2. Projet N°3

Objectif : créer une fenêtre et afficher l'heure.

Réalisation : la création de la fenêtre se fera de manière identique au projet précédent. Seule sera ajoutée la partie permettant l'affichage de l'heure courante.

Télécharger le projet.

Le projet pas à pas :

La création de la fenêtre se fait exactement de la même manière que dans le projet précédent. Nous allons simplement utiliser les fonctions contenues dans stdio.h.

Sélectionnez
#include <Windows.h>
#include <stdio.h>

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
   WNDCLASSEX principale;
   principale.cbSize=sizeof(WNDCLASSEX);
   principale.style=CS_HREDRAW|CS_VREDRAW;
   principale.lpfnWndProc=MainProc;
   principale.cbClsExtra=0;
   principale.cbWndExtra=0;
   principale.hInstance=hInstance;
   principale.hIcon=LoadIcon(NULL,IDI_APPLICATION);
   principale.hCursor=LoadCursor(NULL,IDC_ARROW);
   principale.hbrBackground=reinterpret_cast<HBRUSH>(COLOR_WINDOW+1);
   principale.lpszMenuName=NULL;
   principale.lpszClassName="std";
   principale.hIconSm=LoadIcon(NULL,IDI_APPLICATION);
   RegisterClassEx(&principale);

   HWND hWnd;
   hWnd=CreateWindowEx(
      WS_EX_CLIENTEDGE,
      "std",
      "Notre fenêtre",
      WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      NULL,
      NULL,
      hInstance,
      NULL
   );
   ShowWindow(hWnd,SW_SHOW);

Comme nous allons afficher l'heure, il est important que nous soyons prévenus de manière régulière. Nous allons demander à Windows de nous avertir toutes les secondes, de manière à afficher l'heure régulièrement. Notre fenêtre recevra un message WM_TIMER toutes les secondes.

Sélectionnez
SetTimer(hWnd,NULL,1000,NULL);

La réception des messages se fait exactement de la même manière que dans le projet précédent.

Sélectionnez
   MSG msg;
   while(GetMessage(&msg,NULL,0,0)==TRUE)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return 0;
}

La procédure fonctionne de manière identique que précédemment. Nous allons ajouter le traitement du message WM_TIMER qui sera reçu toutes les secondes. Ce message demandera le redessinement d'une zone de l'écran (la zone ou nous afficherons l'heure).

Sélectionnez
LRESULT CALLBACK MainProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam)
{
   HDC hDC;
   PAINTSTRUCT paintst;
   RECT rcClient;
   switch (mes)
   {
      case WM_TIMER:
         rcClient.top=0;
         rcClient.left=0;
         rcClient.right=100;
         rcClient.bottom=50;
         RedrawWindow(hWnd,&rcClient,NULL,RDW_ERASE|RDW_INVALIDATE|RDW_ERASENOW|RDW_NOCHILDREN);
         return 0;

Un message WM_PAINT sera donc reçu toutes les secondes et à chaque fois que la fenêtre doit être redessinée. La fenêtre doit être redessinée si elle est couverte puis découverte par une autre fenêtre (par exemple).

Nous allons afficher l'heure en utilisant une police spécifique. Une fois cette police créée, il est nécessaire d'indiquer que nous allons l'utiliser dans notre contexte d'affichage (HDC). Le contexte d'affichage (ou Device Context) réfère à la zone client de notre fenêtre et contient tous ses paramètres graphiques (disposition sur l'écran, nombre de couleurs affichables…). Il est identifié par la variable hDC qui nous est passée par Windows au travers de la fonction BeginPaint().

Une fois la police créée et le texte dessiné, il convient de détruire la police.

L'heure courante sera récupérée grâce à la fonction GetLocalTime().

Sélectionnez
   case WM_PAINT:
      char buf[256];
      SYSTEMTIME CurrentTime;
      HFONT hFont;
      hFont=CreateFont(20,0,0,0,700,FALSE,FALSE,FALSE,0,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH|FF_DONTCARE,"Comic Sans MS");
      hDC=BeginPaint(hWnd,&paintst);
      SelectObject(hDC,hFont);
      GetLocalTime(&CurrentTime);
      sprintf(buf,"%d : %d : %d",CurrentTime.wHour,CurrentTime.wMinute,CurrentTime.wSecond);
      TextOut(hDC,0,0,buf,strlen(buf));
      EndPaint(hWnd,&paintst);
      DeleteObject(hFont);
      return 0;
   case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
   default:
      return DefWindowProc(hWnd, mes, wParam, lParam);
   }
}

Il est essentiel de bien comprendre le principe d'affichage dans une fenêtre car il est très utilisé par Windows. Quel que soit le cas, l'affichage se fait toujours entre deux fonctions qui indiquent le début et la fin de l'affichage. Il ne faut jamais oublier de libérer le contexte d'affichage après l'avoir utilisé. Ici, la libération du contexte d'affichage se fait par la fonction EndPaint().

I-D. Lecture dans un fichier et boîte de dialogue

I-D-1. Cours théorique

Nous allons maintenant revenir sur un programme utilisant des ressources. En effet, l'utilisation de boites de dialogues créées en ressources permettent bien souvent de gagner un temps précieux pour des programmes de petites tailles et de faible complexité. C'est le cas du programme que nous allons réaliser ici.

Nous allons donc aborder deux thèmes principaux: les boites de dialogues et les contrôles prédéfinis, ainsi que la lecture d'un fichier par des fonctions de l'API Windows, donc sans utiliser les fonctions du C traditionnel. Les contrôles prédéfinis sont les 'edit boxes', 'combo boxes', 'buttons', etc. Leur utilisation est très simple, car ils sont gérés de manière entièrement autonome. Ils envoient à la fenêtre qui les a créés des messages de notification pour indiquer les actions de l'utilisateur : clic sur le bouton, saisie d'un caractère…

La lecture du fichier se fait de manière relativement simple, et fonctionne par un système de HANDLE (comme beaucoup de fonctions de l'API Windows) qu'il faut créer puis détruire.

I-D-2. Projet N°4

Objectif : créer une boite de dialogue permettant la saisie d'un fichier et affichant les 500 premiers octets de ce fichier.

Réalisation : nous allons ici utiliser une ressource pour créer la boite de dialogue. La lecture du fichier se fera au niveau de la procédure de la boite de dialogue, car elle est très brève. Nous utiliserons ici une fonction qui permet de créer la boite de dialogue et de relever les messages, la partie 'main' du programme sera donc très courte (2 lignes).

Télécharger le projet.

Le projet pas à pas :

Nous allons créer un programme avec un fichier de ressources, inclure les fichiers correspondants (voir parties précédentes). La fonction WinMain() est excessivement simple puisque la fonction DialogBox() s'occupe de créer la boîte de dialogue et de récupérer ses messages. Cette fonction retourne seulement une fois que la boite de dialogue est fermée.

Sélectionnez
#include <Windows.h>

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
   DialogBox(hInstance,(LPCTSTR)IDD_MAIN,NULL,(DLGPROC)MainProc);
   return 0;
}

La procédure de la boîte de dialogue est très simple, elle ne gère que trois messages provenant de trois boutons. IDOK est envoyé lors d'un clic sur le bouton OK, IDCANCEL est envoyé lors d'un clic sur la 'croix' et IDC_LIRE est un message envoyé lors d'un clic sur le bouton IDC_LIRE que nous avons créé. Les variables créées en début de procédure serviront à la lecture du fichier que nous verrons après.

Sélectionnez
LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   char buf[501];
   HANDLE hFile;
   DWORD Read;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_LIRE:

La réelle nouveauté de ce programme va se faire ici. Il s'agit tout d'abord de récupérer le nom de fichier entré par l'utilisateur. Comme il est entré dans un contrôle prédéfini, nous allons utiliser la fonction GetDlgItemText() et nous placerons ce nom de fichier dans le buffer créé au début de cette procédure sous le nom de buf. Une fois ce nom de fichier récupéré, nous allons essayer d'ouvrir le fichier donné en lecture. Si nous n'y parvenons pas, un message d'erreur sera affiché grâce à la fonction MessageBox(). Le traitement du message sera alors arrêté.

Remarque: la fonction MessageBox() ne retourne qu'une fois que la boîte de dialogue créée est fermée. Mais la récupération des messages de notre boîte de dialogue est assurée par MessageBox(). Le traitement prolongé de ce message n'est donc pas gênant dans ce cas puisque les messages continueront d'arriver à notre procédure.

Sélectionnez
         GetDlgItemText(Dlg,IDC_FILENAME,buf,256);
         hFile=CreateFile(buf,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
         if(hFile==INVALID_HANDLE_VALUE)
         {
            MessageBox(Dlg,"Erreur, impossible d'ouvrir le fichier spécifié.","Erreur",MB_OK);
            return 0;
         }

Arrivés à ce stade nous savons que le fichier saisi est valide et qu'il a pu être ouvert. Il ne reste plus qu'à lire les 500 premiers octets. Ils seront placés dans le buffer. Comme cette chaîne vas être affichée, elle doit être terminée par un caractère NULL. C'est pour cette raison que buf a été défini comme un tableau de 501 caractères (500 pour la lecture et 1 pour le caractère NULL). Comme la lecture peut faire moins de 500 octets, nous nous baserons sur la valeur retournée par ReadFile() pour ajouter le caractère NULL. Puis nous afficherons ce buffer dans l'edit box créé a cet effet grâce à SetDlgItemText().

Sélectionnez
         ReadFile(hFile,buf,500,&Read,NULL);
         CloseHandle(hFile);
         buf[Read]='';
         SetDlgItemText(Dlg,IDC_TEXT,buf);
         return 0;

Le reste du traitement de la procédure ne présente pas de difficulté particulière. Il est similaire aux traitements précédents. La seule différence notable est lors de la fermeture de la boîte de dialogue. Comme elle a été créée avec la fonction DialogBox() il ne faut pas utiliser PostQuitMesage() pour la quitter mais EndDialog().

Sélectionnez
      case IDOK:
         EndDialog(Dlg,0);
         return TRUE;
      case IDCANCEL:
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

Ce programme est relativement simple. La gestion de l'interface graphique ne prend que quelques lignes. Nous voyons donc bien l'intérêt de l'utilisation des ressources et donc contrôles prédéfinis. Sans l'aide des ressources, ce programme aurait été beaucoup plus compliqué à réaliser.

I-E. Pas à pas pour C++ Builder

Nous allons reprendre la 4ème partie : "Lecture dans un fichier et boîte de dialogue." mais adaptée à C++ Builder. Deux procédures sont traitées dans ce document une pour BCB6 et une pour BCB4.

Pour les projets sans boîte de dialogue la procédure est la même sauf qu'il n'y a pas besoin de rajouter de fichiers ressources.

I-E-1. BCB 6

  • Faire "Nouveau". (appelle la boîte de dialogue Nouveaux éléments)
  • Sur la boîte de dialogue "Expert console" :
  • Sur "Type de source" sélectionner "C" ou "C++". (Pour l'exemple cela n'a pas d'importance)
  • Aucune des CheckBox à droite ne doit être cochée.
  • Cliquer sur le bouton "Ok".
  • Maintenant nous avons le code minimum pour ce type d'application :
Sélectionnez
#include <windows.h>
#pragma hdrstop

//---------------------------------------------------------------------------
#pragma argsused
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow)
{
        return 0;}
  • Enregister le projet sous le nom choisi. Ici "main.cpp" et "main.bpr". (Faire "Fichier" puis "Tout enregistrer")
  • J'ai utilisé "Resource WorkShop" (livré avec BCB) pour faire le fichier Dialog1.rc mais il peut se faire avec n'importe quel éditeur de ressource.
  • Puis il faut ajouter le fichier ressource au projet:
  • Sur le menu faire "Projet" puis "Ajouter au projet"
  • Selectionner le type de fichier voulu ici "Fichier ressource (*.rc)"
  • Puis sélectionner le fichier ici "Dialog1.rc"
  • Puis faire Ouvrir
  • Le code source est identique a l'exemple Visual C++ (J'ai mis les mêmes noms d'identificateur)
  • Les source des fichiers "Dialog1.rc" et "ressource.h" sont a la fin de ce document.

Fichier "main.cpp" :

Sélectionnez
#include <windows.h>
#pragma hdrstop //Pas obligatoire
#include "ressource.h"

//---------------------------------------------------------------------------

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

#pragma argsused //ajouté automatiquement mais pas nécéssaire.

WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, in
t nCmdShow)

{
     DialogBox(hInstance,(LPCTSTR)DIALOG_1,NULL,(DLGPROC)MainProc);
     return 0;
}
//---------------------------------------------------------------------------

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   char buf[501];
   HANDLE hFile;
   DWORD Read;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_LIRE:
         GetDlgItemText(Dlg,IDC_FILENAME,buf,256);
         hFile=CreateFile(buf,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
         if(hFile==INVALID_HANDLE_VALUE)
         {
            MessageBox(Dlg,"Erreur, impossible d'ouvrir le fichier spécifié.","Erreur",MB_OK);
            return 0;
         }
         ReadFile(hFile,buf,500,&Read,NULL);
         CloseHandle(hFile);
         buf[Read]='';
         SetDlgItemText(Dlg,IDC_TEXT,buf);
         return 0;

      case IDOK:
         EndDialog(Dlg,0);
         return TRUE;
      case IDCANCEL:
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

I-E-2. BCB 4

  • Faire "Nouveau". (appelle la boîte de dialogue Nouveaux éléments)
  • Sur la boîte de dialogue "Nouveaux éléments" Onglet "Nouveau" DbClk sur l'icône "Expert console". (appelle la boîte de dialogue "Expert d'application console")
  • Sur la boîte de dialogue "Expert d'application console" :
  • Sur "Type de fenêtre" sélectionner "Windows (GUI)".
  • Sur "Type d'exécution" sélectionner "EXE".
  • Clicker sur le bouton "Terminer".
  • Maintenant nous avons le code minimum pour ce type d'application :
Sélectionnez
#include <windows.h>
#pragma hdrstop
#include <condefs.h>

//---------------------------------------------------------------------------
#pragma argsused
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
        return 0;
}
  • Enregister le projet sous le nom choisi. Ici "main.cpp". (Faire "Fichier" puis "Tout enregistrer")
  • J'ai utilisé "Resource WorkShop" (livré avec BCB) pour faire le fichier Dialog1.rc mais il peut se faire avec n'importe quel éditeur de ressource.
  • Puis il faut ajouter le fichier ressource au projet:
  • Sur le menu faire "Projet" puis "Ajouter au projet"
  • Sélectionner le type de fichier voulu ici "Fichier ressource (*.rc)"
  • Puis sélectionner le fichier ici "Dialog1.rc"
  • Puis faire Ouvrir
  • Cela va ajouter cette ligne dans le code :
Sélectionnez
USERC("Dialog1.rc");
  • Le reste du code est identique a l'exemple Visual C++ (J'ai mis les mêmes noms d'identificateur)

Fichier "main.cpp" :

Sélectionnez
#include <windows.h>
#pragma hdrstop
#include <condefs.h>
#include "ressource.h"

//---------------------------------------------------------------------------
USERC("Dialog1.rc");
//---------------------------------------------------------------------------

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM );

#pragma argsused //ajouté automatiquement mais pas nécéssaire.
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
{
   DialogBox(hInstance,(LPCTSTR)DIALOG_1,NULL,(DLGPROC)MainProc);
   return 0;
}

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM )
{
   int Select;
   char buf[501];
   HANDLE hFile;
   DWORD Read;
   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_LIRE:
         GetDlgItemText(Dlg,IDC_FILENAME,buf,256);
         hFile=CreateFile(buf,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
         if(hFile==INVALID_HANDLE_VALUE)
         {
            MessageBox(Dlg,"Erreur, impossible d'ouvrir le fichier spécifié.","Erreur",MB_OK);
            return 0;
         }
         ReadFile(hFile,buf,500,&Read,NULL);
         CloseHandle(hFile);
         buf[Read]='';
         SetDlgItemText(Dlg,IDC_TEXT,buf);
         return 0;

      case IDOK:
         EndDialog(Dlg,0);
         return TRUE;
     case IDCANCEL:
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}
  • Autres fichiers
  • Identiques pour BCB4 et BCB6

Fichier "Dialog1.rc"

Sélectionnez
DIALOG_1 DIALOG 14, 30, 212, 227
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "DIALOG_1"
FONT 8, "MS Sans Serif"
{
 CONTROL "Ok", IDOK, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 56, 193, 36, 14
 CONTROL "Cancel", IDCANCEL, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 115, 193, 38, 14
 CONTROL "", IDC_FILENAME, "EDIT", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 26, 18, 91, 12
 CONTROL "", IDC_TEXT, "EDIT", ES_LEFT | ES_MULTILINE | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 18, 49, 175, 119
 CONTROL "Lire", IDC_LIRE, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 136, 18, 50, 14
}

Fichier "Ressource.h"

Sélectionnez
#define DIALOG_1    1

#define IDC_FILENAME 101
#define IDC_TEXT 102
#define IDC_LIRE 103

I-F. Lecture d'un fichier WAV

I-F-1. Cours théorique

A présent, nous allons réaliser un nouveau projet qui sera déjà un programme 'utile' en soi. Evidemment, il restera très sommaire et se contentera de lire un fichier WAV sélectionné par l'utilisateur.

Nous inviterons tout d'abord l'utilisateur à choisir le fichier qu'il veut lire à partir d'une boîte de dialogue 'parcourir'. Une fois que l'utilisateur aura choisi le fichier qu'il désire lire, il pourra choisir de l'écouter. La lecture du fichier WAV se fera grâce à une fonction multimédia fournie par Windows: sndPlaySound(). Cette fonction est entièrement autonome et lira le fichier en arrière plan sans la moindre intervention de la part du programme. Cet automatisme est très pratique puisqu'il permet de lire le fichier sans se soucier de rien. Cependant, les possibilités de cette fonction restent restreintes. On pourra se satisfaire de cette fonction dans quasiment toutes les applications multimédias.

I-F-2. Projet N°5

Objectif : créer une boite de dialogue permettant la saisie d'un fichier WAV puis sa lecture.

Réalisation : nous allons utiliser un fichier ressources qui sera ici très adapté. Le programme ne contiendra que la fonction WinMain() et une procédure pour la boite de dialogue.

Télécharger le projet.

Le projet pas à pas :

Comme dans le programme précédent, nous allons créer un projet d'application Win32, ainsi qu'un fichier de ressources. La fonction WinMain() contiendra simplement l'appel de notre boîte de dialogue. Remarquons tout de même que la fonction sndPlaySound() que nous allons utiliser se trouve dans la librairie winmm.lib qui n'est pas une librairie standard. Il faut donc ajouter cette librairie. Ceci peut être fait grâce au menu Project/Setting/Link/Input. Dans le champ contenant déjà les librairies par défaut, ajoutez winmm.lib. Sans cet ajout, nous obtiendrions une erreur à la compilation indiquant que la fonction ne peut pas être trouvée.

La boîte de dialogue contiendra un champ 'Edit Box' (IDC_NOMDEFICHIER) dans lequel sera affiché le nom du fichier à lire. Nous cocherons la case 'Read Only' dans les options de ce champ de manière à empêcher l'utilisateur de saisir lui-même un nom de fichier. De cette manière, la possibilité de choix erronés se trouve déjà limitée.

Nous ajouterons un bouton 'Parcourir' avec pour identifiant IDC_PARCOURIR et un bouton 'Lire' (IDC_LIRE). Pour quitter, l'utilisateur devra utiliser la 'croix' (ID_CANCEL).

La fonction WinMain() ne diffère pas par rapport au programme précédent (si ce n'est dans les entêtes à inclure). Nous ajouterons une variable globale de manière à pouvoir accéder à l'instance du programme dans toutes les fonctions du programme (et ici dans la procédure de la boîte de dialogue).

Sélectionnez
#include <Windows.h>
#include <mmsystem.h>
#include "ressource.h"

HINSTANCE hInst;

LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
   hInst=hInstance;
   DialogBox(hInstance,(LPCTSTR)IDD_MAIN,NULL,(DLGPROC)MainProc);
   return 0;
}

La procédure de la boîte de dialogue n'est pas très compliquée. Il faudra toutefois y ajouter les boutons 'Lire' et 'Parcourir'. Le bouton parcourir sera le plus difficile à ajouter.

Nous allons réserver un buffer de taille MAX_PATH (qui est une constante marquant la taille maximale d'un nom de fichier), ainsi qu'une structure OPENFILENAME qui nous servira pour la boîte de dialogue 'Parcourir'.

Sélectionnez
LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam)
{
   int Select;
   char buf[MAX_PATH];
   OPENFILENAME DlgInfs;

   switch(message)
   {
   case WM_COMMAND:
      Select=LOWORD(wParam);
      switch(Select)
      {
      case IDC_PARCOURIR:

La fonction qui affiche la boîte de dialogue 'Parcourir' est GetOpenfileName(). De manière à personnaliser cette boîte de dialogue à nos besoins, nous passons une structure contenant les caractéristiques de la boîte de dialogue. Comme nous n'utiliserons pas une grande partie des membres de cette structure, nous allons tous les mettre à 0 grâce à la fonction memset() du C standard. Ceci constitue une bonne habitude à prendre. Pour plus d'informations sur les paramètres et les possibilités de cette fonction référez-vous à l'aide de fournie par Microsoft.

Sélectionnez
         memset(&DlgInfs,0,sizeof(OPENFILENAME));

         DlgInfs.lStructSize=sizeof(OPENFILENAME);
         DlgInfs.hwndOwner=Dlg;
         DlgInfs.hInstance=hInst;
         DlgInfs.lpstrFilter="Fichiers WAV*.WAV";
         DlgInfs.lpstrFile=buf;
         DlgInfs.nMaxFile=MAX_PATH;
         DlgInfs.lpstrTitle="Choisissez le fichier à lire.";
         DlgInfs.Flags=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST;
         if(GetOpenFileName(&DlgInfs))
            SetDlgItemText(Dlg,IDC_NOMDEFICHIER,buf);
         return TRUE;
Lorsque l'utilisateur pressera le bouton 'Lire', il faudra récupérer le nom de fichier saisi dans le champ 'IDC_NOMDEFICHIER' et le lire grâce à la fonction sndPlaySound().
      case IDC_LIRE:
         GetDlgItemText(Dlg,IDC_NOMDEFICHIER,buf,MAX_PATH);

         sndPlaySound(buf,SND_ASYNC);
         return TRUE;
Il ne reste plus qu'à gérer la sortie de la boîte de dialogue. Dans le cas ou l'utilisateur quitte le programme, il faut stopper la lecture du son en cours. Pour cela on utilisera encore la fonction sndPlaySound() avant de quitter.
      case IDCANCEL:
         sndPlaySound(NULL,NULL);
         EndDialog(Dlg,0);
         return TRUE;
      }
   default:
      return FALSE;
   }
}

Ce programme ne présente aucune difficulté particulière. Il se suffit à lui-même bien qu'étant particulièrement pauvre. Ceci permet de donner une idée de la gestion des boîtes de dialogues qui seront dans le futur intégrées à un programme bien plus important. Bien que la gestion de telles boîtes de dialogue constitue un détail dans un programme plus important, il est nécessaire de bien comprendre leur fonctionnement de manière à ne pas perdre de temps et pouvoir créer rapidement une interface graphique agréable.

I-G. Création d'une application console

I-G-1. Cours théorique

Avant de s'intéresser aux applications consoles, il convient de bien comprendre de quoi il s'agit. Une application console n'est pas un programme DOS. C'est un programme Windows utilisant les fonctions de l'API Windows mais qui s'affiche dans une console DOS au lieu d'une fenêtre classique. Un tel programme ne peut pas être démarré en mode DOS réel.

L'avantage de ces programmes est qu'il est inutile de se préoccuper de l'affichage de la fenêtre puisque toutes les interventions sur la fenêtre sont prises en compte par le système. Ce type d'application est très utile pour de petits utilitaires fonctionnant en ligne de commande (bien qu'ils paraissent austères !).

Cependant, ces programmes fonctionnent de la même manière que les applications DOS (pour ce qui est de l'entrée sortie sur l'écran). On pourra donc utiliser les fonctions printf() et scanf() du C standard pour afficher des données à l'écran. De même les fonctions de flux standard C++ cin et cout sont utilisables.

Cette partie ne fera pas l'objet d'un projet, mais les applications console seront utilisées plus tard pour illustrer des programmes simples et ne nécessitant pas ou peu de communications avec l'utilisateur, de manière à réduire la taille des programmes.

I-H. Fenêtre classique ou ressource ?

I-H-1. Cours théorique

Maintenant que nous avons créé et utilisé ces deux types de fenêtres, il convient de s'interroger sur leur utilisation. Faut-il préférer les ressources ou plutôt les fenêtres classiques ? Quels sont les avantages des une et des autres ?

Les fenêtres créées à partir de ressources (souvent les boîtes de dialogue) sont évidemment bien plus rapides à créer. De plus leur utilisation nécessite beaucoup moins de code. Dans ce cas, pourquoi continuer à utiliser des fenêtres classiques créées avec l'API Windows ?

Si les ressources peuvent séduire, il faut cependant s'en méfier. Elles conviennent très bien pour des boîtes de dialogue invitant l'utilisateur à saisir des valeurs, ou à définir certains paramètres. C'est d'ailleurs leur rôle principal. En les utilisant dans ce contexte on gagne un temps très important par rapport à des fenêtres créées “manuellement”. Cependant, l'affichage dans une boîte de dialogue ne se prête que peu à la personnalisation.

Pour afficher des données comme des images, des polices personnalisées ou autre, il reste préférable d'utiliser une fenêtre classique. De même si la fenêtre doit gérer des interventions utilisateur comme une touche entrée, un clic, il faut alors passer à une fenêtre classique. Dans ces cas, la gestion automatisée devient en fait un obstacle à l'interception de ces événements.

Avant de se lancer dans la programmation d'une fenêtre classique ou au contraire de fenêtres ressources, il faut donc réfléchir à l'utilisation qui sera faite de ces fenêtres. Ni l'une ni l'autre de ces méthodes ne sont à bannir. Au contraire, il faut connaître les deux et savoir utiliser l'une ou l'autre dès qu'il le faut. La facilité des ressources ne doit pas en faire un réflexe systématique.

Bien entendu on ne peut pas établir des caractéristiques types pour lesquelles il faut absolument utiliser tel ou tel type de fenêtre. Cette décision doit prendre en compte les exigences du programme... Je ne me risquerais pas à dresser une liste exhaustive de tous les cas ou l'utilisation de telle ou telle méthode est préférable mais j'invite seulement le programmeur à réfléchir avant d'utiliser les ressources.


Java et Eclipse tutoriel JM_Doudoux

Programmation avec l'API Windows

Télécharger le projet ... http://bob.developpez.com/tutapiwin/projets/
  • II. Les boîtes de dialogue
    • II-A. Fonctionnement général
      • II-A-1. Cours théorique
    • II-B. Initialisation
      • II-B-1. Cours théorique
    • II-C. Le contrôle 'Edit'
      • II-C-1. Cours théorique
    • II-D. Le contrôle 'Check Box'
      • II-D-1. Cours théorique
    • II-E. Le contrôle 'Radio Button'
      • II-E-1. Cours théorique
    • II-F. Le contrôle 'List Box'
      • II-F-1. Cours théorique
    • II-G. Le contrôle 'Progress bar'
      • II-G-1. Cours théorique
    • II-H. Le contrôle 'Combo box'
      • II-H-1. Cours théorique
    • II-I. Synthèse sur les contrôles
      • II-I-1. Cours théorique
    • II-J. Deux projets récapitulatifs
      • II-J-1. Cours théorique
      • II-J-2. Projet N°6a
    • II-K. MessageBox()
      • II-K-1. Cours théorique
    • II-L. Parcourir l'arborescence
      • II-L-1. Cours théorique

II. Les boîtes de dialogue

II-A. Fonctionnement général

II-A-1. Cours théorique

Avant de se lancer dans la programmation d'applications plus complexes, il est nécessaire de bien comprendre le schéma de fonctionnement de la boîte de dialogue. Il se rapproche du fonctionnement d'une fenêtre classique sans pour autant être le même. Il conviendra donc d'éviter les analogies entre ces deux types de fenêtres.

La procédure qui gère une boîte de dialogue peut être excessivement simple. Pour toute notification, Windows appelle la procédure et lui demande de traiter le message. Si elle est en mesure de le traiter, elle doit retourner TRUE. Dans le cas contraire elle retourne FALSE et Windows effectuera le traitement par défaut pour le message. Le minimum est donc d'assurer la fermeture de la boite de dialogue grâce à la fonction EndDialog(). En effet, la fermeture de la boite de dialogue ne fait pas partie du traitement par défaut des messages, ce qui est logique puisqu'il est impossible à Windows de 'deviner' le bouton qui doit quitter la boîte de dialogue.

Dès qu'une boite de dialogue traite un message, elle doit donc retourner TRUE (sauf indication contraire). Remarquez la structure du switch avec le default qui retourne FALSE. Dès qu'un message n'est pas traité, la procédure retourne donc FALSE.

On peut considérer le fonctionnement d'une boîte de dialogue en trois temps. Tout d'abord l'initialisation. C'est à ce moment que les différents contrôles sont initialisés. Par exemple, le statut des 'check boxes' sera défini. Cette phase est généralement indispensable, mais dans certains cas, une boîte de dialogue n'aura besoin d'aucune valeur par défaut. L'initialisation se fait avant que la boite de dialogue ne soit affichée, grâce à un message (WM_INITDIALOG). Si ce message n'est pas traité, c'est à dire si la procédure ne contient pas de case WM_INITDIALOG, aucun champ n'aura de valeur particulière.

La deuxième phase de fonctionnement d'une boite de dialogue est celle durant laquelle l'utilisateur effectue les modifications qu'il désire. Il modifie des champs, 'coche' des options… Ce traitement peut se faire de manière entièrement autonome si aucune action particulière n'est nécessaire. Cette phase ne se reflétera donc pas forcément dans la procédure. Dans certains cas cette phase sera inutile, par exemple pour un message d'erreur.

Une fois que l'utilisateur a fait les modifications qu'il désire, il va falloir les 'récupérer'. Le statut de tous les contrôles doit être analysé de manière à modifier les variables au sein même du programme. En effet, les modifications effectuées par l'utilisateur ne se reflètent en rien au niveau des variables du programme. Une fois que le programme à mis à jour toutes les variables nécessaires, la boite de dialogue peut se terminer.

II-B. Initialisation

II-B-1. Cours théorique

L'initialisation d'une boîte de dialogue se fait grâce au message WM_INITDIALOG. Si une boîte de dialogue ne désire pas effectuer de traitement particulier à sa création, elle ignore simplement ce message. Lorsque ce message est envoyé à la boite de dialogue, celle-ci n'est pas encore affiché. C'est à ce moment que les valeurs par défaut des contrôles sont définies. Ces valeurs seront définies grâce à des fonctions de l'API Windows.

De plus, comme la fenêtre n'est pas encore visible, il est possible de modifier sa taille, sa position, sans que cela apparaisse à l'utilisateur (fonction SetWindowPos() par exemple).

La valeur retournée après le traitement du message WM_INITDIALOG détermine si le champ par défaut aura ou non le focus. Le focus est en fait l'entrée clavier. Si un champ possède le focus et qu'une touche est tapée au clavier, c'est à ce champ et à lui seul que cela sera signalé. Dans un champ demandant un mot de passe (par exemple), il est très utile de passer la focus au champ par défaut, de manière à éviter à l'utilisateur de devoir cliquer dans le champ avant de saisir sont mot de passe. Pour que Windows donne le focus au champ par défaut, la procédure doit retourner TRUE. Dans le cas contraire, elle doit retourner FALSE.

II-C. Le contrôle 'Edit'

II-C-1. Cours théorique

Ce contrôle à déjà été vu dans les projets précédents. C'est un des plus simples à utiliser. Comme pour tous les contrôles, on peut distinguer deux aspects à l'utilisation de celui-ci. Le premier aspect est son apparence. Elle est modifiée grâce à l'éditeur de ressources du compilateur. Si vous utilisez Visual C++, il vous suffit de double cliquer sur le contrôle pour accéder à ses propriétés. La deuxième partie du fonctionnement de ce contrôle se fait au travers de la procédure qui gère la boîte de dialogue. Elle consiste en la définition ou la récupération de l'état du contrôle (ici le texte qu'il contient).

Avant d'utiliser le contrôle, il faut donc définir son apparence, ainsi qu'un identifiant, permettant au programme de l'identifier. L'identifiant d'un contrôle est généralement de la forme IDC_ suivi du nom du contrôle tout en majuscules. Le compilateur se charge de maintenir à jour un fichier définissant toutes les constantes utilisées. Pour Visual C++, ce fichier est ressource.h.

Il est possible de personnaliser de nombreuses caractéristiques pour un contrôle de type 'Edit'. Voici une liste regroupant les styles les plus utilisés. Cette liste n'est pas exhaustive, référez-vous à l'annexe A pour plus de précisions. Si vous utilisez un éditeur de ressources, vous devriez pouvoir modifier les styles grâce à une interface graphique. Si éditez vos ressources en mode texte, les styles sont mis entre crochets.

  • Read Only [ES_READONLY] : afficher le contrôle sur fond gris. Il est impossible de modifier le texte, mais il peut toutefois être sélectionné et copié.
  • Disabled : le contrôle est désactivé. Il apparaît en gris et il est impossible de sélectionner le texte. Cette option ne sera accessible qu'à partir d'un éditeur de ressources, car elle ne constitue pas un style. Pour désactiver un contrôle, utilisez la fonction EnableWndow() lors de l'initialisation de la boîte de dialogue.
  • Number [ES_NUMBER] : indique que l'on ne peut saisir que des chiffres dans ce contrôle. Le point n'est pas accepté. Pour entrer un nombre à virgule, il faudra donc utiliser un contrôle classique.
  • Password [ES_PASSWORD] : affiche des étoiles à la place des caractères saisis. Le texte ne peut pas être copié.
  • Multiline [ES_MULTILINE] : indique que le contrôle peut contenir plusieurs lignes. Pour effectuer un retour à la ligne, il faut afficher le caractère \r\n. Au clavier, il faut taper CTRL+ENTER.
  • AutoHScroll [ES_AUTOHSCROLL] - AutoVScroll [ES_AUTOVSCROLL] : indique si le contrôle doit défiler horizontalement ou verticalement. Pour permettre à un contrôle de défiler verticalement il faut désactiver le défilement horizontal.

Une fois que l'apparence du contrôle est convenable, il faut pouvoir lui assigner une valeur ou récupérer la valeur entrée par l'utilisateur. Ces manipulations se font au travers de quatre fonctions :

  • SetDlgItemInt() : permet de définir la valeur d'un contrôle grâce à un entier. Il est possible d'indiquer si cet entier est signé ou non. L'identifiant du contrôle est celui définit dans les ressources (IDC_... en général).
  • GetDlgItemInt() : permet de récupérer la valeur numérique d'un contrôle. Cette fonction échouera si le contrôle contenait du texte.
  • SetDlgItemText() : permet d'afficher une chaîne de caractères dans le contrôle. Cette chaîne de caractères peut à priori avoir n'importe quelle longueur.
  • GetDlgItemText() : permet de récupérer la chaîne de caractères contenue dans le contrôle. Il est indispensable de préciser la longueur maximale de la chaîne qui sera récupérée (elle correspond à la taille du buffer dans lequel elle sera stockée).

II-D. Le contrôle 'Check Box'

II-D-1. Cours théorique

Le contrôle 'Check Box' fait lui aussi partie de l'un des plus simples. Il peut contenir trois états, coché, non coché ou indéterminé. On peut lui attribuer différents styles qui modifient son apparence et parfois son fonctionnement. Par défaut, ce contrôle ne peut être que dans deux états, coché ou non coché. En modifiant le style on peut permettre à l'utilisateur de spécifier un troisième état, indéterminé, qui correspond à une case cochée mais grisée. Voici une liste des styles principaux pouvant être appliqués à ce contrôle (les styles modifiant l'apparence graphique du contrôle ne sont pas traités ici). Pour une liste exhaustive des différents types consultez l'annexe A.

  • Tri-state [BS_3STATE] [BS_AUTO3STATE] : le contrôle peut prendre trois états au lieu de deux.
  • Auto : [BS_AUTO3STATE] [BS_AUTOCHECKBOX] : permet de créer un contrôle dont l'état est modifié automatiquement lorsque l'utilisateur l'utilise. Si ce style n'est pas activé, la boîte de dialogue reçoit un message WM_COMMAND et doit elle-même modifier l'état du contrôle.

Comme on pouvait s'y attendre, il suffit de deux fonctions pour le manipuler, une pour modifier son état, et l'autre pour récupérer son état.

  • CheckDlgButton() : permet de modifier l'état du contrôle. Il suffit de spécifier la boite de dialogue qui le contient ainsi que son identifiant et un paramètre commandant l'état du contrôle. Les trois états possibles sont BST_CHECKED, BST_INDETERMINATE et BST_UNCHECKED. Le contrôle sera redessiné automatiquement, c'est à dire que lorsque la fonction retournera, l'état graphique du contrôle aura été modifié.
  • IsDlgButtonChecked() : permet de récupérer l'état du contrôle. Cette fonction retourne un entier non signé qui peut prendre une des trois valeurs présentées ci-dessus.

L'utilisation de contrôles 'Check box' se résume donc à peu de choses. Elle pourra être mise en parallèle avec les contrôles 'Radio buttons', qui fonctionnement de manière similaire, mais avec un système de groupes.

II-E. Le contrôle 'Radio Button'

II-E-1. Cours théorique

Le contrôle 'Radio Button' est assez similaire au contrôle 'Check Box', si ce n'est qu'un seul de ces contrôles peut être coché à la fois. Il se présente généralement sous forme d'un rond à cocher. Bien entendu, vous pouvez utilisez plusieurs de ces contrôles dans un même fenêtre et créer des groupes, de sorte qu'un seul des contrôle par groupe ne puisse être sélectionné, et non pas un seul pour l'ensemble de la fenêtre.

Si vous désirez créer seulement un groupe dans une fenêtre, il est inutile de créer un groupe. En effet, par défaut, un seul contrôle peut être sélectionné dans l'ensemble de la fenêtre.

Si vous utilisez l'éditeur de ressources de Visual, la création de groupes est simplifiée. Pour créer un groupe de radio buttons, il faut en insérer un, et lui mettre le style 'Group'. Puis on insère les autres radios du groupe mais sans mettre le style 'Group'. Pour créer un nouveau groupe, il suffit de refaire la même manipulation autant de fois que nécessaire.

Si vous éditez vos ressources en mode texte, le principe est le même. On met le style WS_GROUP au premier des radios puis on insère les autres à la suite.

L'ensemble des styles est décrit dans l'annexe A. La plupart modifient seulement l'apparence graphique du contrôle et ne sont pas très difficiles à utiliser. Retenez simplement un style assez important :

  • Auto [BS_AUTORADIOBUTTON] : ce contrôle indique que les radios sont gérés automatiquement. Vous n'avez pas à demander de décocher les radios lorsqu'un autre est coché. Généralement, ce style est toujours utilisé, sauf cas très particuliers.

Pour cocher ou décocher les radios, il faut utiliser les fonctions ChekDlgButton() et IsDlgButtonChecked(). Ces fonctions sont identiques à celles utilisées avec les contrôles 'Check Box'. Bien entendu, le style indéterminé n'est pas disponible ici. Remarquons que lorsque vous utilisez CheckDlgButton(), les autres radios ne sont pas décochés, même en utilisant le style Auto. Enfin, vous pouvez utilisez la fonction CheckRadioButton() pour cocher un radio et décochez les autres. Pour cela, les radios doivent avoir étés créés dans l'ordre (les identifiants doivent être en ordre croissant). Il suffit ensuite de passer l'identifiant du premier radio, celui du dernier, ainsi que le radio à sélectionner.

II-F. Le contrôle 'List Box'

II-F-1. Cours théorique

Le contrôle 'List Box' est plus complexe que ceux étudiés précédemment. Il ne permet pas seulement de contenir un état entré par l'utilisateur, il permet d'afficher des informations sous forme de liste, de trier la liste par ordre alphabétique… Ce contrôle aura donc de multiples utilisations, d'autant plus qu'il est relativement simple à utiliser. Il ne se manipule pas grâce à des fonctions mais au travers de messages, qui seront envoyés par l'intermédiaire de la fonction SendMessage().

La liste peut être présentée sous deux formes : soit en une seule colonne et avec un défilement vertical, soit sur plusieurs colonnes et avec un défilement horizontal. Les colonnes auront obligatoirement la même taille. Elles ne peuvent pas comporter d'en-têtes.

Voici les styles applicables à ce contrôle. Pour une liste exhaustive, consultez l'annexe A. Remarquez que par défaut, c'est la sélection simple qui est utilisée.

  • Sort [LBS_SORT] : détermine si le contrôle trie ou non la liste par ordre alphabétique.
  • Multi-Column [LBS_MULTICOLUMN] : permet d'afficher la liste sur une ou plusieurs colonnes. Si ce style est activé, il faut utiliser un défilement horizontal.
  • Horizontal Scroll [WS_HSCROLL] : défilement horizontal de la liste.
  • Vertical Scroll [WS_VSCROLL] : défilement vertical de la liste.
  • Multiple Selection [LBS_MULTIPLESEL] : autorise la sélection multiple. L'utilisateur peut sélectionner les éléments de la liste en cliquant dessus.
  • Extended Selection [LBS_EXTENDEDSEL] : autorise la sélection multiple. L'utilisateur sélectionne les éléments de la liste grâce aux touches SHIFT et CTRL.
  • No Selection [LBS_NOSEL] : interdit toute sorte de sélection.

L'utilisation du contrôle est relativement simple. Nous ne verrons ici que les tâches élémentaires, ajout et suppression d'un élément, récupération de la sélection courante. La méthode de récupération de la sélection courante dépend bien entendu du mode de sélection choisi. Pour une sélection unique, cette action est simple. Elle s'avère un peu plus délicate pour un contrôle à sélections multiples. L'ensemble des messages utilisables avec ce contrôle est présenté dans l'annexe A. Voici les messages les plus simples :

  • LB_ADDSTRING : ajoute un élément à la liste et demande le tri de la liste si celle-ci à le style 'Sort'.
  • LB_INSERTSTRING : ajoute un élément à la liste et le place à une position déterminée. Après l'insertion, la liste n'est pas triée.
  • LB_SETITEMDATA : permet d'associer une valeur 32 bits à un élément de la liste. Cette valeur peut par exemple être un pointeur sur les données que représente cet élément.
  • LB_DELETESTRING : supprime l'élément désigné.
  • LB_GETCURSEL : retourne la sélection courante pour un contrôle à sélection unique.
  • LB_GETSELITEMS : place dans un tableau l'ensemble des éléments sélectionnés dans un contrôle à sélections multiples.
  • LB_SETCURSEL : modifie la sélection courante dans un contrôle à sélection unique.
  • LB_GETCOUNT : retourne le nombre total d'éléments contenus par le contrôle.

II-G. Le contrôle 'Progress bar'

II-G-1. Cours théorique

Le contrôle 'Progress Bar' est l'un des plus simples à utiliser. Ses styles étant définis au niveau ressources, l'utilisation se borne à la définition de la position. Pour cela, il faut tout d'abord donner deux valeurs numériques qui constituent le minimum et le maximum de la barre de progression. Une fois cela fait, il ne reste plus qu'à fournir une position courante.

Avant de pouvoir utiliser ce contrôle, il faut penser à l'initialiser. On doit placer en début de programme un appel à la fonction InitCommonControls() ou à la fonction InitCommonControlsEx(), de manière à forcer le chargement des dll permettant d'utiliser ce contrôle. L'appel à l'une de ces deux fonctions peut être fait à n'importe quel moment (mais avant d'utiliser le contrôle) et ne doit être fait qu'une seule fois. Si cet appel n'est pas fait, la boîte de dialogue de s'affichera pas.

Les styles pouvant être utilisés avec ce contrôle sont simples, ils sont donnés dans l'Annexe A.

Voici les messages utilisés pour commander ce contrôle :

  • PBM_SETRANGE permet de définir les valeurs numériques du minimum et du maximum pouvant être atteints. Les valeurs de cet intervalle, tout comme la position courante sont des entiers, il faut donc prévoir un intervalle assez large pour ne pas que l'on distingue de saccades lors de la progression. Cet intervalle doit donc être au moins de la taille (en pixels) de la barre. De manière générale, on peut opter pour un intervalle 0 - 1000.
  • PBM_SETPOS définit la position actuelle de progression. Cette valeur doit bien entendu être comprise dans l'intervalle fourni par le message PBM_SETRANGE.

II-H. Le contrôle 'Combo box'

II-H-1. Cours théorique

Le contrôle 'Combo Box' est l'un des plus complexes. Il peut servir dans différents 'modes de fonctionnement'. On peut l'utiliser pour permettre à l'utilisateur de faire un choix dans un menu déroulant ou permettre à l'utilisateur d'entrer un choix personnalisé. Nous ne verrons ici que l'utilisation de ce contrôle en tant que menu déroulant permettant à l'utilisateur de faire un choix dans une liste prédéterminée. C'est l'utilisation la plus pratique de ce contrôle, car elle permet de s'assurer que le choix fait est valide.

Voici un aperçu des styles applicables à ce contrôle :

  • CBS_DROPDOWN est le style correspondant au type de 'Combo box' que nous avons décrit. La liste est affichée si l'utilisateur clique sur le contrôle et elle ne peut pas être modifiée.
  • CBS_SORT permet d'activer le tri automatique du contrôle par ordre alphabétique.

Pour utiliser ce contrôle, vous pouvez utiliser les messages suivants :

  • CB_ADDSTRING permet d'ajouter une entrée à la liste. La liste sera triée si le style CBS_SORT est utilisé.
  • CB_SETCURSEL permet de définir la sélection courante.
  • CB_GETCURSEL permet de retourner la sélection courante.
  • CB_SETITEMDATA permet d'associer une valeur 32 bits à une entrée. Cette valeur peut être utilisée pour identifier les entrées si la liste est triée par exemple.
  • CB_GETITEMDATA retourne la valeur 32 bits associée avec l'entrée spécifiée.

Pour déterminer la taille de ce contrôle (notamment la hauteur de la liste), on pourra utiliser la fonction SetWindowPos() qui sera vue plus tard.

II-I. Synthèse sur les contrôles

II-I-1. Cours théorique

Les contrôles qui ont été présentés précédemment l'ont été de manière brève. Ce tutoriel ne prétend absolument pas fournir une référence exhaustive de toutes les possibilités d'utilisation des contrôles. Cette présentation des contrôles les plus courants permettra simplement de se familiariser avec leur utilisation. En effet, quel que soit le type de contrôle utilisé, la méthode pour le manipuler reste toujours la même.

Une fois le principe de fonctionnement des contrôles compris, il sera facile de l'étendre à une utilisation plus complexe. Les contrôles prédéfinis offrent une excellente manière de guider l'utilisateur et de le limiter. De plus, leur utilisation demande relativement peu de code et permet de créer rapidement une interface agréable. Les contrôles offrent de nombreuses fonctionnalités qu'il faut savoir exploiter.

On remarquera cependant que ces contrôles sont assez difficilement personnalisables. Pour créer une interface graphique plus poussée ou plus personnalisée, il faudra donc recourir à d'autres méthodes que des contrôles. Il est tout de même possible de personnaliser les contrôles (couleurs, etc.) mais ces méthodes ne permettant une personnalisation complète de l'interface.

Il faut tout de même rappeler que les ressources ne sont pas le seul moyen de mettre en place les contrôles, ils constituent simplement un 'raccourci' pour leur utilisation. Tous les contrôles peuvent être intégrés de manière dynamique dans une fenêtre ou un boîte de dialogue en utilisant la fonction CreateWindowEx(). Dans le cas d'une interface dynamique, c'est à dire ne se présentant pas systématiquement sous la même forme, l'utilisation des ressources est fortement déconseillée car elle 'fixe' les contrôles au moment de la compilation. La création des contrôles au moment de l'affichage permet donc une plus grande souplesse, bien qu'exigeant une mise en place plus contraignante.

II-J. Deux projets récapitulatifs

II-J-1. Cours théorique

Voici 2 projets qui présentent des utilisations extrêmement simples de quelques contrôles élémentaires. Seul le projet 06a sera décrit ici, le second étant très similaire.

II-J-2. Projet N°6a

Ces projets présentent une utilisation extrêmement simple de quelques contrôles élémentaires. Aucune vérification n'est faite quant à la validité des entrées faites par l'utilisateur. Remarquons que du fait des contrôles utilisés, l'utilisateur ne peut pas fournir de sélection non valide (à part entrer un nom vide). C'est d'ailleurs pour ça qu'il est indispensable d'initialiser les contrôles 'Radio' ou 'Combo box' car sans cela, on pourrait avoir une sélection non valide (aucune sélection en fait). Les contrôles sont bien entendus initialisés lors du message WM_INITDIALOG (à ce moment, la boîte de dialogue n'est pas encore affichée).

La liste n'est modifiée qu'au moment où l'utilisateur presse le bouton 'Ajouter'. A ce moment, on récupère le statut de tous les contrôles que l'on a placés et on vérifie éventuellement la validité des données entrées. Si tout est correct, on ajoute les données voulues à la liste. Ici, on aurait quelques peines à récupérer les informations contenues dans la liste au moment de la validation de la boîte de dialogue. Pour faciliter cela, il faudrait stocker chaque ligne dans une structure appropriée. On associerait ensuite à chaque entrée de la liste son index dans notre tableau grâce au message LB_SETITEMDATA.

Télécharger les projets commentés [VC++] : Projet 06a - Projet 06b.

Télécharger les projets commentés [BCB] : Projet 06a - Projet 06b.

II-K. MessageBox()

II-K-1. Cours théorique

La fonction MessageBox() permet d'afficher une boite de dialogue prédéfinie contenant le texte spécifié. Elle est très utile pour afficher des messages d'erreurs, informer l'utilisateur. Elle retourne l'identifiant du bouton qui a été pressé pour la quitter. Cette fonction ne permet quasiment aucune personnalisation de la boîte de dialogue qu'elle affiche, elle n'est donc réellement utile que dans le cas de messages envoyés à l'utilisateur, souvent de manière informative avec pour seul choix le bouton 'Ok'.

Les types de boites de dialogue pouvant être affichés sont :

  • MB_ABORTRETRYIGNORE
  • MB_OK
  • MB_OKCANCEL
  • MB_RETRYCANCEL
  • MB_YESNO
  • MB_YESNOCANCEL

Les icônes suivantes peuvent être affichées sur la boîte de dialogue :

  • MB_ICONEXCLAMATION
  • MB_ICONWARNING
  • MB_ICONINFORMATION
  • MB_ICONASTERISK
  • MB_ICONQUESTION
  • MB_ICONSTOP
  • MB_ICONERROR
  • MB_ICONHAND

La fonction retournera l'une des valeurs suivantes :

  • IDABORT
  • IDCANCEL
  • IDIGNORE
  • IDNO
  • IDOK
  • IDRETRY
  • IDYES

II-L. Parcourir l'arborescence

II-L-1. Cours théorique

Pour permettre à l'utilisateur de sélectionner un dossier ou un fichier, on utilisera respectivement les fonctions SHBrowseForFolder() et GetOpenFileName(). Ces deux fonctions affichent les boîtes de dialogue standard de Windows pour parcourir l'arborescence à la recherche d'un dossier ou d'un fichier. Notons que la constante MAX_PATH désigne la longueur standard pour un chemin. Les buffers chargés de recevoir les noms de fichiers ou les chemins sont supposés faire au moins cette taille.

Voici un exemple d'utilisation de la fonction SHBrowseForFolder() :

Sélectionnez
BROWSEINFO bi;
LPITEMIDLIST Item;
// Ici, la taille du buffer ne peut pas être passée
// buffer est supposé être de taille MAX_PATH (ou plus)
char buffer[MAX_PATH];

// On met tous les champs inutilisés à 0
memset(&bi,0,sizeof(BROWSEINFO));
// hDlg est le HWND de la boite de dialogue qui demande l'ouverture
// Ou NULL si la boite de dialogue n'a pas de fenêtre parent
bi.hwndOwner=Dlg;
// Contient le répertoire initial ou NULL
bi.pidlRoot=NULL;
bi.pszDisplayName=buffer;
bi.lpszTitle="Répertoire courant";
bi.ulFlags=NULL;
bi.lParam=NULL;
Item=SHBrowseForFolder(&bi);
if(Item!=NULL)
{
    // buffer contient le nom du répertoire sélectionné
    SHGetPathFromIDList(Item,buffer);
    // buffer contient le chemin complet de la sélection
}

Voici un exemple d'utilisation de la fonction GetOpenFileName() :

Sélectionnez
OPENFILENAME st;
char buffer[MAX_PATH];

// Pas de fichier par défaut
buf[0]='';
// On met tous les champs inutilisés à 0
memset(&st,0,sizeof(OPENFILENAME));
st.lStructSize=sizeof(OPENFILENAME);
// hDlg est le HWND de la boite de dialogue qui demande l'ouverture
// Ou NULL si la boite de dialogue n'a pas de fenêtre parent
st.hwndOwner=hDlg;
// La syntaxe est : Description1Filtre1Description2Filtre2
st.lpstrFilter="Executables - Fichiers de commandes*.exe;*.bat";
st.lpstrFile=buffer;
st.nMaxFile=MAX_PATH;
st.lpstrTitle="Recherche de l'exécutable";
st.Flags=NULL;
// Contient le répertoire initial ou NULL
st.lpstrInitialDir=NULL;
if(GetOpenFileName(&st))
    // buffer contient notre chemin


Java et Eclipse tutoriel JM_Doudoux

Programmation avec l'API Windows

Télécharger le projet ... http://bob.developpez.com/tutapiwin/projets/
  • III. Les fenêtres
    • III-A. Fenêtres et boîtes de dialogue
      • III-A-1. Cours théorique
    • III-B. Fonctionnement général d'une fenêtre
      • III-B-1. Cours théorique
    • III-C. Récupération des messages
      • III-C-1. Cours théorique
    • III-D. Création d'une fenêtre
      • III-D-1. Cours théorique
    • III-E. Destruction d'une fenêtre
      • III-E-1. Cours théorique
    • III-F. Contexte d'affichage
      • III-F-1. Cours théorique
    • III-G. Gestion de base d'une fenêtre
      • III-G-1. Cours théorique
    • III-H. Interactions avec l'utilisateur
      • III-H-1. Cours théorique
    • III-I. Les timers
      • III-I-1. Cours théorique
    • III-J. Un projet récapitulatif
      • III-J-1. Cours théorique
    • III-K. Affichage de texte dans une fenêtre
      • III-K-1. Cours théorique
    • III-L. Utilisation de polices personnalisées
      • III-L-1. Cours théorique
    • III-M. Affichage d'une image
      • III-M-1. Cours théorique
    • III-N. Dessin
      • III-N-1. Cours théorique
    • III-O. Fond personnalisé
      • III-O-1. Cours théorique
    • III-P. Commandes systèmes
      • III-P-1. Cours théorique
    • III-Q. Menus
      • III-Q-1. Cours théorique
    • III-R. Création dynamique d'un contrôle
      • III-R-1. Cours théorique

III. Les fenêtres

III-A. Fenêtres et boîtes de dialogue

III-A-1. Cours théorique

Les boîtes de dialogue sont semblables aux fenêtres dans le sens où ce sont des fenêtres. Cependant, il faut être bien attentif à marquer la différence qu'il existe entre ces deux types de fenêtres. Les boîtes de dialogue sont gérées en grande partie par le système. Créer une boite de dialogue est relativement simple. Les couleurs de fond, le type de fenêtre ont des valeurs par défaut si l'on utilise un éditeur de ressources.

Dans le cas des fenêtres, il va falloir tout définir lors de l'exécution. Lors de la création d'une fenêtre, il sera nécessaire de préciser un grand nombre de paramètres qui étaient passés implicitement au système grâce aux ressources avec les boîtes de dialogue. Il faut donc bien considérer les boîtes de dialogue comme un cas 'simplifié' de fenêtres.

Cependant, on ne peut pas appliquer toutes les méthodes fonctionnant sur les fenêtres aux boîtes de dialogues ou inversement. Le mode de gestion des boîtes de dialogue est différent de celui des fenêtres, particulièrement au niveau de l'affichage. De manière générale, il faut utiliser les boîtes de dialogue avec des contrôles prédéfinis. Pour créer des fenêtres contenant un affichage personnalisé, des images, des graphiques, du texte utilisant différentes polices, il est préférable d'utiliser une fenêtre. C'est donc pour ce type de travaux que les boîtes de dialogues trouvent leurs limites.

Il faut donc retenir que les fenêtres et les boîtes de dialogue ne fonctionnement pas de la même manière (malgré de nombreuses similitudes). Copier - Coller la procédure d'une fenêtre pour une boîte de dialogue ne serait pas une bonne idée car certains messages sont spécifiques aux fenêtres et d'autres aux boîtes de dialogue.

III-B. Fonctionnement général d'une fenêtre

III-B-1. Cours théorique

Le fonctionnement général des fenêtres a déjà été vu au cours du premier chapitre. Un petit rappel ainsi que quelques précisions est cependant nécessaire.

Lorsqu'une fenêtre est créée, le système lui attribue un identifiant. Cet identifiant est appelé 'handle'. Il identifié la fenêtre dans son ensemble. Toute modification de la fenêtre (déplacement, modification de la taille) se fait grâce à cet identifiant. Une application utilisant plusieurs fenêtres pourra donc être amenée à définir des variables globales contenant les identifiants des différentes fenêtres pour plus de confort. Cette définition globale n'est absolument pas nécessaire et peut être évitée dans le cas d'applications simples (le passage en paramètre des identifiants devient rapidement impossible).

La fenêtre d'une application ne correspond absolument pas à l'application elle même. Une application (ou processus) peut être exécutée et ne pas avoir de fenêtre. La création de la fenêtre est effectuée par le programme lui même. De plus, elle est totalement facultative. Aucune fenêtre ne sera créée par le système au moment de l'exécution de l'application. De plus, la fermeture de la fenêtre n'entraîne pas la fermeture de l'application. C'est au programmeur de faire concorder ces deux événements si il le désire. Bien entendu la majorité des applications créent une fenêtre au démarrage et se terminent lors de la fermeture de cette même fenêtre, mais ce n'est en rien une obligation. De plus, une fenêtre peut avoir plusieurs états, visible, cachée, minimisée... Lorsqu'une fenêtre est créée, elle n'est pas visible. Elle existe, ont peut la déplacer, afficher dans sa zone client... Cependant, rien de tout ceci n'est visible à l'utilisateur. Dès qu'on donne à une fenêtre le statut 'visible', elle apparaît à la même position que lorsqu'elle était invisible. Lorsqu'on détruit une fenêtre, elle n'est plus affichée à l'écran, mais dans ce cas, on ne peut plus la modifier. Elle n'existe plus pour le système. Il ne faut donc pas confondre une fenêtre cachée, qui existe toujours bien que n'étant pas visible, et une fenêtre détruite, qui elle n'existe plus.

Une fenêtre contient différentes zones. La zone client est la zone (généralement blanche) dans laquelle les données de la fenêtre sont affichées. Les menus, la barre de titre, ne font pas partie de la zone client. La zone client est la seule partie de la fenêtre qui soit gérée par l'application. La taille de la fenêtre sur l'écran est donc différente de celle de la zone client. La zone client est nécessairement de taille égale ou inférieure à l'espace occupé par la fenêtre. Pour afficher des données dans une fenêtre, on utilise des coordonnées relatives à la zone client. Pour déplacer une fenêtre, on utilise des coordonnées relatives à l'écran (coin supérieur gauche).

Pour identifier la zone client, on utilise un contexte d'affichage. Il définit l'ensemble des propriétés relatives à l'affichage dans la zone client (nombre de couleurs disponibles, type de police...). Un contexte d'affichage identifie une zone dans laquelle ont peut afficher n'importe quel type de données. Cette zone n'est pas nécessairement affichée à l'écran, on peut par exemple afficher des données dans une fenêtre cachée en utilisant le même contexte d'affichage. Dans ce cas, le contexte d'affichage est lié à la fenêtre, on peut donc l'obtenir à partir de l'identifiant de la fenêtre concernée. Pour afficher dans la zone client, il n'est donc jamais nécessaire de se préoccuper de la position de la fenêtre sur l'écran, il suffit de posséder un 'handle' sur le contexte d'affichage.

III-C. Récupération des messages

III-C-1. Cours théorique

La réception des messages et leur transmission à la procédure de fenêtre appropriée sont essentiels. L'application ne doit jamais cesser 'd'écouter' au cas où de nouveaux messages arriveraient. Si l'application arrête de transmettre les messages, la fenêtre semblera 'bloquée' (affichage persistant, impossibilité de la déplacer…).

La récupération des messages peut se faire grâce à deux fonctions : GetMessage() ou PeekMessage(). La différence entre ces deux fonctions est fondamentale, il ne faut donc pas les assimiler.

GetMessage() est une fonction bloquante. Elle attend qu'au moins un message soit présent dans la file d'attente de la fenêtre pour retourner. Le temps d'attente est infini, tant qu'aucun message n'est placé dans la file d'attente.

PeekMessage() est une fonction non bloquante. Elle vérifie si des messages sont présents et retourne immédiatement. Si au moins un message était présent, elle le retourne, sinon elle ne retourne aucun message. De plus, cette fonction permet de ne pas supprimer les messages de la file d'attente. On peut donc consulter des messages et laisser la boucle de messages principale effectuer le traitement.

L'utilisation de ces deux fonctions s'inscrit dans des cadres totalement différents. En général, GetMessage() est utilisé dans une boucle while. Si aucun message n'est présent, GetMessage() signale au système qu'il peut donner le processeur à une autre application. Placer cette fonction dans une boucle ne génère donc pas d'occupation processeur tant qu'aucun message n'est présent dans la file d'attente. PeekMessage() ne fonctionne pas de cette manière. Placer cette fonction dans une boucle while génère une occupation processeur de 100 %. La fonction sera effectivement appelée un nombre indéfini de fois, occupant ainsi le processeur. La récupération principale des messages doit donc se faire avec la fonction GetMessage().

Cependant, si l'application est occupée, elle peut appeler de manière régulière PeekMessage() suffisamment de fois pour traiter tous les messages. Par exemple, toutes les secondes, PeekMessage() est appelée jusqu'à épuisement de la file d'attente. Le traitement des messages sera plus lent mais il sera effectué quand même. Cette solution évite de devoir faire appel au multi-threading (exécution simultanée de plusieurs parties du programme).

La différence entre ces deux fonctions doit donc être clairement comprise. L'utilisation de l'une ou l'autre de ces fonctions à tort peut s'avérer catastrophique pour les performances du programme et du système dans son ensemble. Dans le cas d'une application n'effectuant pas de tâches lourdes, l'utilisation de PeekMessage() ne sera pas nécessaire. L'interruption du traitement des messages pour une durée courte (jusqu'à un dixième de seconde) est sans conséquences. Il faut tout de même remarquer que GetMessage() ne sera pas rappelée tant que le traitement du message courant n'est pas terminé. Le traitement des messages par la procédure doit donc si possible être bref.

Voici un exemple classique de boucle réalisant le traitement de messages :

Sélectionnez
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
  TranslateMessage(&msg);
  DispatchMessage(&msg);
}

La fonction GetMessage() retourne FALSE si le message WM_QUIT est reçu. Ce message est envoyé à la fenêtre principale pour demander l'arrêt de l'application. Par défaut, ce message n'est pas envoyé lors de la destruction de la fenêtre.

III-D. Création d'une fenêtre

III-D-1. Cours théorique

Avant de pouvoir créer une fenêtre, il faut définir un type. C'est ce type de fenêtre qui sera utilisé pour afficher la fenêtre nouvellement créée. Il est possible d'utiliser des types prédéfinis ou de créer son propre type de fenêtres. Les contrôles utilisés par les boites de dialogues sont des types de fenêtres prédéfinis. Pour définir un nouveau type de fenêtres, on utilise la fonction RegisterClassEx() (cf. chapitre 1).

Une fois le type de fenêtre définit, il faut créer la fenêtre. Pour cela, on utilise la fonction CreateWindowEx() (cf. chapitre 1). On lui passe le type (la classe) de fenêtre désiré ou encore une classe prédéfinie. Lorsque la fenêtre est créée, un message WM_CREATE sera envoyé. C'est à ce moment que l'application doit faire les initialisations qu'elle désire.

La fenêtre créée est initialement cachée. Il faut donc faire un appel à ShowWindow() pour l'afficher. Il est possible de passer à ShowWindow() le paramètre nCmdShow que Windows passe à WinMain(), de cette manière l'état de base de la fenêtre peut être paramétré dans un raccourci.

III-E. Destruction d'une fenêtre

III-E-1. Cours théorique

La destruction d'une fenêtre se traduit par l'envoi à la fenêtre concernée d'un message WM_DESTROY. Il ne faut pas confondre ce message avec le message WM_QUIT. WM_QUIT demande la fermeture totale de l'application, le message WM_DESTROY demande simplement la destruction de la fenêtre en question. Le fait de cliquer sur la 'croix' provoque l'envoi d'un message WM_DESTROY à la fenêtre. Pour faire quitter l'application en même temps que la fermeture de la fenêtre, on peut utiliser la fonction PostQuitMessage() qui elle, envoie un message WM_QUIT. Cette fonction doit être appelée dès la réception du message WM_DESTROY. Le 'handle' de la fenêtre devient invalide dès la destruction de la fenêtre. Pour tester la validité d'un 'handle', on peut utiliser la fonction IsWindow().

Lorsque ce message est reçu, la fenêtre est déjà en cours de destruction. Il est possible de traiter le message WM_CLOSE pour demander une autre action que la destruction de la fenêtre. On peut par exemple, masquer la fenêtre. Une pression sur la 'croix' provoquera donc simplement la disparition à l'écran de la fenêtre, mais pas au niveau système. La fenêtre pourra par la suite être réaffichée dans le même état, par un appel à la fonction ShowWindow().

III-F. Contexte d'affichage

III-F-1. Cours théorique

Un contexte d'affichage (DC) identifie une zone dans laquelle l'application peut afficher tout type de données. La zone identifiée peut être située à n'importe quel endroit de l'écran, elle peut être déplacée, ou même masquée par d'autres fenêtres, sans que l'application en ai conscience. Le fait de déplacer une fenêtre ne change donc rien à la manière dont l'application redessine son contenu. Il en est de même pour une fenêtre cachée ou n'étant pas au premier plan. Toute fenêtre possède un contexte d'affichage associé à sa zone client. Pour obtenir ce contexte, on peut utiliser la fonction GetDC() en spécifiant la fenêtre concernée. Si on spécifie un paramètre NULL, la fonction retourne un contexte d'affichage sur la totalité de l'écran. La zone identifiée couvre l'ensemble de l'écran et est située au-dessus de toutes les fenêtres affichées. Sauf cas très particulier, on n'utilise jamais le contexte d'affichage de la zone écran pour dessiner. Il peut être utilisé lors de la création de bitmaps, etc.

Le contexte d'affichage contient toutes les informations relatives à l'affichage (police, nombre de couleurs)... Il n'identifie pas nécessairement une zone écran. Il peut par exemple identifier une feuille dans le cas d'une impression. On pourra donc utiliser les mêmes fonctions pour afficher à l'écran ou pour envoyer des données imprimables mais en modifiant le contexte d'affichage utilisé.

Lorsqu'on modifie une propriété du contexte d'affichage (ex. : la police courante), on dit qu'on sélectionne un objet dans le contexte. Cet sélection est effectuée grâce à la fonction SelectObject(). Différents types d'objets peuvent être sélectionnés dans un contexte d'affichage (polices, images, pinceaux de dessin…).

III-G. Gestion de base d'une fenêtre

III-G-1. Cours théorique

La gestion d'une fenêtre se fait à deux niveaux : au niveau de la procédure qui gère tous les messages reçus par la fenêtre et par des fonctions permettant diverses manipulation sur la fenêtre.

Voici un rapide aperçu des fonctions les plus utiles pour manipuler les fenêtres :

  • SetWindowText() modifie le texte de la barre de titre.
  • GetWindowText() retourne le texte de la barre de titre.
  • SetWindowPos() modifie la taille de la fenêtre, sa position sur l'écran, ainsi que sa position par rapport aux autres fenêtres (Z-order).
  • GetWindowPlacement() retourne des informations sur le statut courant de la fenêtre spécifiée.
  • GetClientRect() retourne la taille de la zone client de la fenêtre.
  • GetWindowRect() retourne la taille totale occupée par la fenêtre sur l'écran.
  • AdjustWindowRect modifie la taille de la fenêtre spécifiée.
  • GetParent() retourne la fenêtre parent de la fenêtre spécifiée.
  • SetParent() modifie la fenêtre parent de la fenêtre spécifiée.
  • ShowWindow() modifie l'état courant d'une fenêtre.
  • IsIconic(), IsZoomed(), IsWindowVisible() retournent des informations sur l'état de la fenêtre courante.
  • GetWindow() retourne la fenêtre ayant une relation spécifiée dans le Z-order par rapport à la fenêtre passée en paramètre.

La procédure de fenêtre, quant à elle, gère l'ensemble des messages envoyés à une fenêtre. Comme les messages envoyés à une fenêtre sont extrêmement nombreux, il serait très pénible de devoir tous les traiter, en particulier pour des fenêtres simples. L'API Windows fournit la fonction DefWindowProc() qui propose un traitement par défaut de tous les messages envoyés à une fenêtre. Pour un traitement personnalisé du message, il ne faut pas appeler la fonction DefWindowProc(). Le traitement par défaut de la plupart des messages est en général satisfaisant. Cependant, pour imposer des comportements personnalisés à une fenêtre, il s'avère obligatoire d'effectuer un traitement personnalisé de certains messages (ex. : pour masquer la fenêtre lors d'une pression sur la 'croix').

Voici un aperçu rapide des messages les plus courants :

  • WM_CREATE est envoyé à une fenêtre au moment de sa création.
  • WM_PAINT est envoyé lorsqu'une partie de la zone client doit être redessinée.
  • WM_ERASEBKGND est utilisé pour demander l'effacement d'une partie de la zone client.
  • WM_SYSCOMMAND est envoyé pour le traitement de différents événements pouvant survenir (fenêtre minimisée, restaurée, …).
  • WM_ACTIVATE est envoyé lorsqu'une fenêtre est activée ou désactivée.
  • WM_MOVE est envoyé lorsque la fenêtre a été déplacée.
  • WM_SIZE est envoyé lorsque la taille de la fenêtre à été modifiée.
  • WM_CLOSE indique que l'utilisateur demande la fermeture de l'application (en cliquant sur la 'croix' ou en pressant ALT+F4).
  • WM_DESTROY indique que la fenêtre est détruite.

Ces messages sont les premiers à utiliser pour le maniement d'une fenêtre. De nombreux autres seront utilisés pour gérer les interactions avec l'utilisateur (souris, clavier). Ils seront vus plus tard. Dans la plupart des cas, seul un nombre relativement peu élevé de messages sera traité par l'application elle-même. Les autres seront passés à la fonction DefWindowProc().

III-H. Interactions avec l'utilisateur

III-H-1. Cours théorique

Une fenêtre, peut recevoir des données de la part de l'utilisateur de deux manières : par le clavier, ou par la souris. Cependant, la fenêtre ne recevra d'information du système que si elle est active. Si la fenêtre n'est pas active elle ne sera informée ni des événements clavier, ni des événements souris. La fenêtre sera informée de différents événements : pression d'une touche, relâchement d'une touche, mouvement de la souris, pression d'un bouton de la souris… Une fenêtre est informée dès que la souris bouge au-dessus de sa zone client (si elle est active). Le nombre de messages envoyés lorsque la souris se déplace est assez important. Aussi, le traitement de ces messages (WM_MOUSEMOV) doit être bref.

Les messages suivants sont utilisés pour traiter les événements provenant du clavier :

  • WM_KEYDOWN est envoyé lorsqu'une touche est enfoncée. Ce message indique le 'Virtual Key Code' correspondant à la touche.
  • WM_KEYUP est envoyé lorsqu'une touche est relevée. Ce message indique le 'Virtual Key Code' correspondant à la touche.
  • WM_CHAR indique le caractère ASCII correspondant à la touche pressée.
  • WM_SYSKEYUP, WM_SYSKEYDOWN, WM_SYSCHAR sont utilisés pour identifier les événements dus à des touches systèmes.

Remarquons tout de même la différence entre un code ASCII et un 'Virtual Key Code' : le code ASCII identifie un caractère (a,A,E,* ont des codes ASCII différents). Un 'Virtual Key Code' identifie une touche physique, a et A ont donc le même code, mais dans le 2e cas, SHIFT est aussi pressé (en ASCII SHIFT n'a pas de code, ce qui est normal car cette touche ne correspond pas à un caractère).

Les messages suivants sont utilisés pour traiter les événements provenant de la souris :

  • WM_MOUSEMOVE est envoyé à chaque déplacement de la souris (plusieurs dizaines de fois par seconde). Les coordonnées de la souris sont passées en paramètres lors de l'envoi de ce message. Si le message est reçu avec un retard, la position reçue sera celle enregistrée lorsque le message a été envoyé.
  • WM_LBUTTONDOWN est envoyé lorsque le bouton gauche de la souris est pressé.
  • WM_LBUTTONUP est envoyé lorsque le bouton gauche de la souris est relevé.
  • WM_MBUTTONDBLCLK est envoyé lors d'un double clic du bouton gauche de la souris. Ce message n'est envoyé que si le style CS_DBLCLKS est utilisé lors de la création du style de la fenêtre.

Les mêmes messages sont envoyés pour les boutons centraux et droits de la souris. (WM_MBUTTON_ ou WM_RBUTTON_).

Pour obtenir la position courante de la souris, une application peut utiliser la fonction GetCursorPos(). Cette position peut ensuite être convertie grâce aux fonctions ScreenToClient() et ClientToScreen(). Les fonctions GetKeyState() et GetKeyboardState() peuvent être utilisées pour connaître l'état d'une touche ou de l'ensemble du clavier.

III-I. Les timers

III-I-1. Cours théorique

Parfois, il peut être utile d'exécuter des tâches à intervalles fixes. Il existe de nombreux moyens d'effectuer cela, mais une des méthodes les plus simples est d'utiliser les 'timers' fournis par l'API Windows. Les 'timers' ne conviennent qu'à des résolutions de temps faibles, en général pas plus de 10 Hz. Un message est envoyé à intervalles réguliers à une fenêtre spécifiée. L'utilisation des timers pour des fréquences trop importantes peut nuire aux performances générales du système du fait des nombreux messages à envoyer. De plus, le temps de traitement des messages fait qu'il est impossible d'obtenir des intervalles réguliers inférieurs à 50ms. Les timers ne constituent donc qu'une méthode simple pour synchroniser un programme dans des conditions assez restreintes. Cependant, ils conviennent pour la plupart des applications bureautiques.

Les timers sont généralement utilisés avec des fenêtres, cependant, ils peuvent être utilisés sans fenêtres. Dans ce cas, le message sera envoyé au thread qui a demandé la création du timer. La fonction GetMessage() doit donc être appelée avec un argument hWnd égal à NULL de manière à na pas associer la réception des messages à une fenêtre spécifique.

Pour créer un timer, on utilise la fonction SetTimer() en précisant l'intervalle de temps désiré. Un message WM_TIMER sera envoyé à la fenêtre à chaque intervalle de temps. Pour stopper un timer, on utilise la fonction KillTimer(). Comme les timers ont des identifiants, il est possible de créer différents timers avec des intervalles différents. L'identifiant du timer est passé en paramètre lors de la réception du message WM_TIMER.

Voici un exemple utilisant un timer sans fenêtre :

Sélectionnez
MSG msg;
SetTimer(NULL,NULL,60000,NULL);

while(GetMessage(&msg,NULL,0,0))
{
  if(msg.message==WM_TIMER)
    MessageBox(NULL,"Déjà une minute de passé!","Info",MB_OK);
}

III-J. Un projet récapitulatif

III-J-1. Cours théorique

Le projet présenté ici est simple. Il reprend la même création de fenêtre qu'au chapitre 1.2. La différence réside dans le traitement des messages. Tout d'abord, un timer est utilisé pour permettre l'affichage de la fenêtre après deux secondes seulement. La fenêtre est initialement cachée. Le timer est créé lors de la réception du message WM_CREATE. Ce n'est pas une nécessité. Il aurait aussi bien pu être créé après l'appel à CreateWindowEx() mais le fait de créer le timer dans la procédure permet de regrouper l'ensemble des traitements relatifs à la fenêtre dans sa procédure.

Des boîtes de dialogue sont affichées lorsque divers événements sont détectés, pression d'un bouton (gauche ou droit), pression d'une touche. Les clics hors de la zone client ne sont pas pris en charge. Comme la position récupérée par GetCursorPos() est relative à l'écran, il faut la convertir en coordonnées relatives à la zone client. La fonction ScreenToClient() fournie pas l'API Windows est donc la plus appropriée.

La destruction de la fenêtre est suivie de la fermeture de l'application grâce un appel de PostQuitMessage().

Télécharger le projet commenté.

III-K. Affichage de texte dans une fenêtre

III-K-1. Cours théorique

Pour afficher du texte dans la zone client d'une fenêtre, il faut posséder un 'handle' sur le contexte d'affichage de cette fenêtre. Le texte qui sera affiché le sera avec la police courante ainsi que les couleurs de premier plan et de fond courantes. Pour afficher du texte, il existe diverses méthodes. La première est d'utiliser la fonction TextOut(). Cette fonction affiche le texte sur une seule ligne, sans jamais calculer de retour à la ligne. Si le texte dépasse la taille de la fenêtre, il sera tronqué. La fonction DrawText() permet quant à elle de définir un rectangle dans lequel le texte doit être écrit. Si le texte dépasse la largeur, il sera mis à la ligne, et ceci autant de fois qu'il faudra.

La position passée pour dessiner le texte correspond par défaut au coin supérieur gauche de la chaîne de texte qui sera dessinée. Le texte sera donc dessiné vers la droite et vers le bas par rapport au point donné. On peut modifier le point de référence pour le positionnement du texte avec la fonction SetTextAlign(). Pour aligner du texte à droite, on pourra par exemple placer ce point dans le coin supérieur droit. Le texte sera donc dessiné à gauche et en bas de la position passée pour dessiner le texte.

La position passée pour dessiner le texte correspond par défaut au coin supérieur gauche de la chaîne de texte qui sera dessinée. Le texte sera donc dessiné vers la droite et vers le bas par rapport au point donné. On peut modifier le point de référence pour le positionnement du texte avec la fonction SetTextAlign(). Pour aligner du texte à droite, on pourra par exemple placer ce point dans le coin supérieur droit. Le texte sera donc dessiné à gauche et en bas de la position passée pour dessiner le texte.

Pour connaître la taille d'un texte à l'écran (en pixels), il faut utiliser la fonction GetTextExtentPoint32(). Cette fonction retourne la taille qu'occupera un texte donné dans un contexte d'affichage donnée. En effet, la taille du texte dépend du contexte d'affichage car la modification de la police entraîne une modification de la taille occupée par le texte. L'utilisation de polices personnalisées sera vue dans le chapitre suivant.

La fonction suivante dessine un texte en rouge centré dans la zone client de la fenêtre spécifiée.

Sélectionnez
void DrawCenteredText(HWND hWnd,char *text)
{
  HDC hDC;
  RECT rcClient;
  GetClientRect(hWnd,&rcClient);
  hDC=GetDC(hWnd);
  SetTextColor(hDC,0x000000FF);
  SetBkMode(hDC,TRANSPARENT);
  SetTextAlign(hDC,TA_CENTER|TA_TOP);
  TextOut(hDC,(int)((float)rcClient.right/2),5,text,strlen(text));
  ReleaseDC(hWnd,hDC);
}

III-L. Utilisation de polices personnalisées

III-L-1. Cours théorique

Pour utiliser une police personnalisée pour l'affichage du texte, il faut tout d'abord créer la police spécifiée. Pour cela, on réalise un appel à CreateFont() pour déterminer la police voulue, la taille, le style… Il faut ensuite sélectionner la police dans le contexte d'affichage avec la fonction SelectObject(). Une fois qu'une police est sélectionnée dans un contexte d'affichage, chaque texte affiché le sera avec cette police.

SelectObject() retourne le 'handle' de la police précédemment sélectionnée. Une fois l'utilisation de la police terminée, l'application doit resélectionner l'ancienne police par un nouvel appel à SelectObject(). Ensuite, elle doit détruire la police inutilisée par un appel à DeleteObject().

La suppression des objets créés par une application est faite automatiquement lorsque celle-ci se termine. Cependant, ils occupent de la place inutilement durant l'exécution de l'application. Il est donc important de supprimer les objets inutilisés. De plus, le nombre d'objets disponible pour une application donnée est limité, si l'application recrée une nouvelle police à chaque modification de l'affichage sans supprimer l'ancienne, elle va rapidement atteindre le nombre maximal d'objets.

Voici un exemple d'appel à CreateFont() :

Sélectionnez
HFONT Police;
Police=CreateFont(
  20,
  0,
  0,
  0,
  700,
  FALSE,
  FALSE,
  FALSE,
  0,
  OUT_DEFAULT_PRECIS,
  CLIP_DEFAULT_PRECIS,
  DEFAULT_QUALITY,
  DEFAULT_PITCH | FF_DONTCARE,
  "Comic Sans MS"
  );

III-M. Affichage d'une image

III-M-1. Cours théorique

Avant de pouvoir afficher une image, il faut tout d'abord la charger. Nous ne traiterons ici que les images bitmap. En effet, ce sont les seules images prises en charge par l'API Windows. Pour effectuer des traitements sur les images avec des fonctions du GDI (Graphic Device Interface), il faut donc tout d'abord les convertir au format bitmap. Les images sont considérées comme des objets du GDI, au même titre que les polices. On peut donc utiliser les fonctions de manipulation d'objet (ex. : SelectObject() ou DeleteObject()) aussi bien sur des images que sur des polices.

On peut charger une image à partir du disque ou encore à partir des ressources du programme. Pour cela, on peut utiliser deux fonctions : LoadBitmap() ou LoadImage(). La fonction LoadBitmap() est beaucoup moins complète que LoadImage(), il est donc préférable d'utiliser cette dernière. Ce n'est cependant pas une obligation. Une fois l'image chargée en mémoire, le GDI retourne un 'handle' (de type HBITMAP) sur l'image. Elle peut alors être affichée.

Le résultat de l'affichage dépend bien entendu du contexte d'affichage utilisé (en particulier du nombre de couleurs qu'il comprend). Pour afficher l'image, on utilisera la fonction DrawState(). Cette fonction n'affiche pas nécessairement des bitmaps, elle peut aussi afficher des icônes par exemple. De plus, cette fonction est capable de modifier la taille d'affichage du bitmap. Il n'est cependant pas recommandé d'agrandir la taille des images à l'affichage car l'agrandissement est alors de très mauvaise qualité.

Voici un exemple de fonction dessinant une image chargée depuis un fichier dans la fenêtre spécifiée :

Sélectionnez
void PrintBmp(HWND hWnd, char *filename)
{
  HBITMAP hBmp;
  HDC hDC;
  hBmp=(HBITMAP)LoadImage(NULL,filename,IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
  hDC=GetDC(hWnd);
  DrawState(hDC,NULL,NULL,(LPARAM)hBmp,NULL,0,0,0,0,DST_BITMAP);
  DeleteObject(hBmp);
  ReleaseDC(hWnd,hDC);
}

III-N. Dessin

III-N-1. Cours théorique

L'affichage de 'dessins' (lignes, cercles, points) dans une fenêtre passe par le GDI. Les dessins se font avec un pointeur. Par défaut, le pointeur est un pixel unique. Tracer une ligne donne donc le résultat attendu : une ligne de 1 pixel de large. Pour dessiner une ligne plus large, il va falloir remplacer le pointeur courant par un pointeur plus large. Pour cela, on utilise le fonction CreatePen(). Elle retourne une variable de type HPEN. Ensuite, il faut sélectionner le pointeur ainsi créé dans le contexte d'affichage courant. Bien entendu, il faudra, tout comme avec les polices, supprimer les pointeurs dès que ceux-ci ne sont plus utilisés.

Voici un aperçu des fonctions utilisées pour dessiner :

  • SetPixel() dessine un pixel unique, indépendamment du pointeur courant.
  • GetPixel() retourne la couleur du pixel à la position spécifiée.
  • MoveToEx() déplace la position courante du curseur de dessin.
  • LineTo() trace une ligne depuis la position courante du curseur de dessin jusqu'à la position spécifiée. La ligne est tracée avec le pointeur courant.
  • Arc(), ArcTo() et AngleArc() dessinent des ellipses ou des portions d'ellipses.
  • Polyline() dessine une série de lignes connectées.

III-O. Fond personnalisé

III-O-1. Cours théorique

Jusqu'à présent, nous avions laissé Windows effacer le fond. L'effacement de la zone client avant le redessinement n'est pas nécessaire, la méthode la plus simple et la plus rapide est de redessiner par-dessus l'ancienne zone. Toutefois, si l'on veut redessiner une partie de la zone client seulement ou si le nouveau dessin ne couvre pas toute la zone (du texte avec fond transparent par exemple) il est nécessaire d'effacer d'abord.

Pour cela, il faut utiliser le message WM_ERASEBKGND. Lorsque ce message est envoyé, un 'handle' sur le contexte d'affichage à effacer est passé en paramètre. Il faut impérativement utiliser le 'handle' passé en paramètre pour effectuer toutes les opérations de dessin. L'application dispose de plusieurs solutions pour effacer la zone client. Elle peut effectuer des tâches complexes, afficher des images, puis un texte… Si les images ne couvrent pas toutes la zone, il faut effacer les parties qui resteront visibles. Pour cela, on peut utiliser la fonction FillRect(). Cette fonction ne prend pas en paramètre une couleur mais un HBRUSH. On peut créer des 'brushes' à partir d'images bitmap avec CreatePatternBrush() ou encore à partir d'une couleur unie avec CreateSolidBrush(). Si l'application désire simplement modifier la couleur de fond de sa zone client, elle n'est pas obligée de traiter le message WM_ERASEBKGND. Elle peut simplement utiliser CreateSolidBrush() et passer le résultat au membre hbrBackground de la structure WNDCLASSEX lors de la création de la classe de la fenêtre. De cette manière la fonction DefWindowProc() écrasera le fond elle-même avec la couleur demandée.

III-P. Commandes systèmes

III-P-1. Cours théorique

Pour personnaliser une application, on peut être amené à modifier le traitement standard des commandes systèmes. Pour cela, on utilise le message WM_SYSCOMMAND. Ce message est envoyé en diverses occasions : déplacement de la fenêtre, utilisation du menu de contrôle sur la barre de titre (fermer, minimiser, restaurer), activation de l'écran de veille, déroulement du menu démarrer… En interceptant ce message, une application peut définir des réactions personnalisées aux traitements standard Windows. Par exemple, masquer la fenêtre au lieu de la détruire lors d'un clic sur la 'croix'. Comme le même message est envoyé (WM_SYSCOMMAND) quel que soit la commande système, il faut impérativement appeler DefWindowProc() pour traiter les commandes systèmes non prises en charge par l'application.

III-Q. Menus

III-Q-1. Cours théorique

Il existe deux manières d'insérer des menus dans une application. La première méthode est de créer un menu grâce à un éditeur de ressources puis de l'insérer dans le programme. La seconde méthode est dynamique, grâce aux fonctionnalités de l'API Windows. De manière générale, les menus obtenus par des ressources seront utilisés pour les menus principaux des fenêtres, pour créer des barres de menus. Pour les menus contextuels, boîtes à outils, …, il est préférable d'utiliser des menus dynamiques. A chaque clic dans un des menus, la fenêtre possédant le menu reçoit un message WM_COMMAND indiquant l'identifiant de l'option choisie.

Pour obtenir une barre de menus à partir d'un fichier de ressources, il suffit de créer le menu dans l'éditeur de ressources puis de passer son identifiant au membre lpszMenuName de la structure WNDCLASSEX. Le menu sera inséré automatiquement dans la fenêtre.

Pour un menu dynamique, on utilisera l'API Windows. Les menus sont identifiés par une variable HMENU. Il faut tout d'abord créer le menu. Il sera initialement vide. Pour cela, il faut utiliser la fonction CreatePopupMenu(). Une fois le menu créé, on peut lui ajouter des éléments (options, sous menus, séparateurs) avec les fonctions AppendMenu() ou InsertMenuItem(). Une fois le menu créé, il faut le dessiner. Pour cela, on utilise les fonctions TrackPopupMenu() ou TrackPopupMenuEx(). Pour une boîte à outils, il suffira de dessiner le menu à la position courante obtenue par un appel à GetCursorPos(). Si le menu est associé à une fenêtre, il sera supprimé automatiquement lors de la destruction de la fenêtre. Dans le cas contraire, l'application doit appeler la fonction DestroyMenu() pour libérer les ressources.

La fonction suivante dessine un menu contextuel :

Sélectionnez
void PrintMenu(HWND hWnd)
{
  HMENU hMenu;
  POINT pt;
  GetCursorPos(&pt);
  hMenu=CreatePopupMenu();
  AppendMenu(hMenu,MF_STRING,1,"Item 1");
  AppendMenu(hMenu,MF_STRING,2,"Item 2");
  AppendMenu(hMenu,MF_SEPARATOR,NULL,NULL);
  AppendMenu(hMenu,MF_STRING,3,"Item 3");
  TrackPopupMenu(hMenu,NULL,pt.x,pt.y,0,hWnd,NULL);
}

Dans cet exemple, les valeurs 1, 2 et 3 passées à AppendMenu() représentent les identifiants des options. Ces valeurs seront passées en paramètre lors de l'envoi du message WM_COMMAND. Elles n'ont aucune signification particulière, si ce n'est pour le programmeur lui-même.

III-R. Création dynamique d'un contrôle

III-R-1. Cours théorique

La création dynamique de contrôles ne présente aucune difficulté particulière. Elle se fait grâce à la fonction CreateWindowEx(), en utilisant des classes de fenêtres prédéfinies. Une fois le contrôle créé, il faut tout de même remarquer que son utilisation est quelque peu différente. En effet, des fonctions telles que SetDlgItemText() ne fonctionneront plus. Il faudra utiliser la fonction SetWindowText() à la place, puisque le contrôle est une fenêtre. La fonction SetDlgItemText() n'est en fait qu'un raccourci qui appelle SetWindowText() après avoir utilisé GetDlgItem() pour obtenir le 'handle' du contrôle correspondant.

Les styles utilisés dans l'éditeur de ressources sont les mêmes. La fonction CreateWindowEx() acceptera donc comme styles l'ensemble des styles utilisés dans l'éditeur de ressources.

Les classes suivantes sont prédéfinies : BUTTON, COMBOBOX, EDIT, LISTBOX, MDICLIENT, RichEdit, RICHEDIT_CLASS, SCROLLBAR, STATIC.


Java et Eclipse tutoriel JM_Doudoux

Programmation avec l'API Windows

Télécharger le projet ... http://bob.developpez.com/tutapiwin/projets/
  • IV. Le système de fichier
    • IV-A. Introduction
      • IV-A-1. Cours théorique
    • IV-B. Création et ouverture d'un fichier
      • IV-B-1. Cours théorique
    • IV-C. Lecture/Écriture dans un fichier
      • IV-C-1. Cours théorique
    • IV-D. Manipulations sur les fichiers
      • IV-D-1. Cours théorique
    • IV-E. Enumération de fichiers
      • IV-E-1. Cours théorique
    • IV-F. Manipulations sur les dossiers
      • IV-F-1. Cours théorique
    • IV-G. Manipulations des chemins
      • IV-G-1. Cours théorique

IV. Le système de fichier

IV-A. Introduction

IV-A-1. Cours théorique

L'API Windows fournit une suite de fonctions permettant d'accéder au système de fichier. Bien entendu, il est toujours possible d'utiliser les fonctions classiques du C (fopen(), fprintf()) pour accéder au système de fichier. Cependant, ces fonctions ne supportent pas toutes les possibilités du système de fichier Windows (ex. : la gestion du partage des ressources dans l'environnement Windows). Il est donc en général préférable d'utiliser les fonctions fournies avec l'API Windows pour manipuler le système de fichier.

Les fonctions de manipulation de fichiers (ouverture, écriture, lecture) seront donc abordées dans ce paragraphe. De plus, l'utilisation de fichiers dans un contexte multitâches impose des précautions d'emploi supplémentaires. Le non respect de certaines précautions simples peut entraîner des erreurs ou même la perte ou l'écrasement de données.

Le problème de la recherche de fichiers dans le système de fichier sera également abordé.

Ce paragraphe a donc pour but de présenter de manière générale les opérations de base lors de la manipulation du système de fichier Windows grâce aux fonctions de l'API Windows.

IV-B. Création et ouverture d'un fichier

IV-B-1. Cours théorique

L'API Windows fournit la fonction CreateFile() pour ouvrir ou créer des fichiers. Cette fonction permet de gérer le partage du fichier au niveau système, les attributs de ce fichier. Il est important de bien comprendre les paramètres principaux de cette fonction.

Avant la description plus en détail de cette fonction, il est important de bien comprendre le fonctionnement du système de fichiers sous Windows. Comme cet environnement est multitâches, il est possible qu'un fichier soit accédé en même temps par plusieurs programmes ou même par le même programme. Le premier programme à demander l'accès au fichier doit préciser s'il souhaite ou non le partager. Si le fichier n'est pas partagé, le système refusera tout autre appel à CreateFile() pour accéder à ce fichier. Le fichier ne sera donc plus accessible au reste du système. Le fichier restera 'réservé' jusqu'à ce que le programme se termine ou jusqu'à ce que la fonction CloseHandle() soit appelée (cette dernière fonction sera expliquée plus tard). En ne partageant pas le fichier le programme s'assure qu'il est le seul à accéder au fichier.

Un fichier peut également être partagé en lecture. Dans ce cas, seul le programme ayant accédé au fichier en premier aura un accès en lecture/écriture. Les appels suivants à CreateFile() permettront seulement d'accéder au fichier en lecture.

Si le fichier est partagé en lecture/écriture, alors l'accès simultané en lecture ou écriture à ce fichier est autorisé. Plusieurs programmes peuvent donc lire ou écrire des données simultanément dans ce fichier. Ce dernier mode de partage est très dangereux et ne doit être utilisé qu'en connaissance de cause.

Lors du premier appel à CreateFile(), le mode de partage du fichier est défini. Il ne pourra pas être modifié par un autre appel. Même si le fichier est partagé, son mode de partage ne pourra pas être modifié. Si l'appel à la fonction CreateFile() réussit, le système retourne un HANDLE au programme. Ce HANDLE identifie le fichier. La fonction CloseHandle() permet de refermer le fichier. À partir de cet appel, le HANDLE identifiant le fichier n'est plus valide.

L'identifiant du fichier stocke également la position courante du pointeur. Le système stockera donc la position courante d'écriture pour chaque nouvel appel à CreateFile(). Si le fichier est ouvert 10 fois simultanément, le système stockera 10 positions. Le partage du fichier n'entraîne donc aucune modification dans les méthodes de lecture/écriture sur le fichier (le pointeur courant ne sera pas modifié même si un autre programme accède le fichier). Cependant, si deux threads utilisent le même HANDLE pour accéder à un fichier, alors le pointeur pourra être modifié par l'un ou par l'autre. Si le même thread fait deux appels successifs à l'API pour lire le fichier, rien ne garantit qu'il lira deux blocs successifs. En effet, si le deuxième thread a modifié le pointeur entre les deux appels, alors la lecture se poursuivra à la nouvelle position.

Pour éviter des complications, le plus simple est généralement de ne pas partager les fichiers ou d'autoriser seulement le partage en lecture.

Voici un détail de principaux arguments de la fonction CreateFile() :

Sélectionnez
HANDLE CreateFile(
  LPCTSTR lpFileName,          // pointer to name of the file
  DWORD dwDesiredAccess,       // access (read-write) mode
  DWORD dwShareMode,           // share mode
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
                               // pointer to security attributes
  DWORD dwCreationDisposition,  // how to create
  DWORD dwFlagsAndAttributes,  // file attributes
  HANDLE hTemplateFile         // handle to file with attributes to
                               // copy
);
  • lpFileName est le nom du fichier à ouvrir ou à créer.
  • DwDesiredAccess est le type d'accès demandé : lecture (GENERIC_READ), écriture (GENERIC_WRITE) ou les deux. Si le fichier est partagé en lecture seulement et qu'un accès en écriture est demandé, la fonction retournera une erreur.
  • DwShareMode indique le mode de partage demandé. Ce mode peut être non partagé (NULL), partagé en lecture (FILE_SHARE_READ), partagé en écriture (FILE_SHARE_WRITE) ou les deux.
  • DwCreationDisposition indique la méthode utilisée pour lire ou créer le fichier. CREATE_NEW demande la création d'un fichier. Si le fichier existe déjà, la fonction retournera une erreur. CREATE_ALWAYS demande la création d'un fichier. Si le fichier existe déjà, il est écrasé. OPEN_EXISTING demande l'ouverture d'un fichier existant. Si le fichier n'existe pas, le fonction retourne une erreur. PEN_ALWAYS demande l'ouverture du fichier. Si le fichier n'existe pas il est créé.
  • dwFlagsAndAttributes indique les attributs utilisés pour créer le fichier.

La fonction retournera un HANDLE en cas de succès ou INVALID_HANDLE_VALUE, c'est à dire le pointeur 0xFFFFFFFF est cas d'erreur.

Dès que le fichier n'est plus utilisé, l'application peut utiliser CloseHandle() pour signifier qu'elle n'utilise plus le fichier spécifié. Toute autre application pourra alors avoir accès à ce fichier si elle le demande.

IV-C. Lecture/Écriture dans un fichier

IV-C-1. Cours théorique

Avant de pouvoir lire ou écrire dans un fichier, il faut réaliser un appel à CreateFile(). Le HANDLE retourné permettra d'identifier le fichier dans lequel on souhaite effectuer les opérations. La fonction ReadFile() permet la lecture dans un fichier. Elle déplace le pointeur courant à la nouvelle position. ReadFile() lit le nombre d'octets spécifiés et retourne un indicateur booléen, ainsi que le nombre d'octets lus. ReadFile() retournera toujours TRUE si le pointeur sur le fichier est valide. Pour détecter la fin du fichier, il faut comparer le nombre d'octets lus au nombre d'octets demandés. Si 256 octets sont demandés à la lecture et que ReadFile() indique que seulement 18 octets ont été lus, alors la fin du ficher est atteinte.

La fonction WriteFile() a un mode de fonctionnement similaire à ReadFile(). Elle écrit le nombre d'octets demandé, retourne un indicateur de succès ainsi que le nombre d'octets effectivement écrits.

Pour déplacer le pointeur courant on utilise la fonction SetFilePointeur(). La nouvelle position peut être spécifiée à partir du début du fichier, de la position courante du fichier ou de la fin du fichier. Cette fonction retourne la nouvelle position. Pour obtenir la position courante du pointeur, on appelle SetFilePointeur() en demandant un déplacement nul à partir de la position courant. On récupère ainsi la position courante du pointeur.

La fonction SetEndOfFile() détermine la position de la fin du fichier. La fin du fichier est alors placée à la position courante du pointeur.

IV-D. Manipulations sur les fichiers

IV-D-1. Cours théorique

D'autres fonctions peuvent être utilisées lors de la manipulation de fichiers. Pour obtenir la taille d'un fichier, il faut utiliser GetFileSize(). Cette fonction retourne sur 32 ou 64 bits la taille du fichier spécifié.

Les fonctions MoveFileEx(), CopyFileEx() et DeleteFile() peuvent être utilisées pour les manipulations courantes sur les fichiers. Ces fonctions ne nécessitent pas que le fichier soit précédemment ouvert par CreateFile(). La fonction DeleteFile() supprime un fichier sans le faire passer par la corbeille. Il est donc impossible d'annuler la suppression du fichier. Pour envoyer simplement un fichier dans la corbeille, il faut utiliser la fonction SHFileOperation(). Cette fonction permet à la fois la suppression et l'envoi à la corbeille d'un fichier.

Le système de fichier VFAT (utilisé sous Windows 95/98) manipule des noms courts et des noms longs. Les noms de fichiers courts (format 8.3) sont un héritage des précédents systèmes de fichiers utilisés. Pour convertir des noms de fichiers en version courte ou longue on utilise les fonctions GetShortPathName() et GetFullPathName().

IV-E. Enumération de fichiers

IV-E-1. Cours théorique

Pour énumérer des fichiers, l'API Windows offre une sélection de trois fonctions : FindFirstFile(), FindNextFile() et FindClose(). La fonction FindFirstFile() initialise la recherche et retourne le premier fichier trouvé (s'il y en a un). Elle retourne un HANDLE sur la recherche en cours, pour permettre de poursuivre la recherche. Le fonction FindNextFile() doit ensuite être appelée pour récupérer l'ensemble des fichiers trouvés. Généralement cette fonction sera appelée dans une boucle. La fonction FindClose() referme le HANDLE et libère la mémoire occupée.

Cette suite de fonctions permet d'effectuer des recherches dans un dossier. Attention, les sous dossiers ne seront pas parcourus. La recherche peut porter sur un listage non exhaustif, par exemple, listage des fichiers *.txt. Pour un listage exhaustif, il faudra demander le listage des fichiers *.*. Le type de recherche, ainsi que le dossier de recherche sont passés à la fonction FindFirstFile() sous forme d'une chaîne de caractère de type : Chemin\\Masque (ex. c:\*.*). Aucune fonction n'est fournie pour le parcours récursif des dossiers de manière à explorer les dossiers et sous dossiers. Pour effectuer une recherche complète, il faut effectuer des appels successifs à ces trois fonctions de manière à parcourir l'ensemble des sous dossiers. Lors d'une recherche, les pseudo-dossiers '.' et '..' sont également listés. Il peut donc être utile d'effectuer un test pour supprimer ces dossiers du listage final. Les informations concernant les fichiers et dossiers listés sont placées dans une structure WIN32_FIND_DATA (attributs, nom, taille…).

IV-F. Manipulations sur les dossiers

IV-F-1. Cours théorique

L'API Windows offre peu de manipulations possibles sur les dossiers. La fonction CreateDirectory() permet la création d'un dossier. Attention toutefois au fait que cette fonction ne crée pas les sous dossiers nécessaires. Si la création du dossier demandé demande la création implicite de plusieurs dossiers, la fonction retournera une erreur. Par exemple, le dossier c:\a\b ne sera créé que si le dossier c:\a existait déjà. Pour créer ce dossier, il faudra faire deux appels à CreateDirectory() pour créer respectivement les dossiers c:\a et c:\a\b.

Pour supprimer un dossier, on utilisera la fonction RemoveDirectory(). Cette fonction ne supprimera le dossier que si il est vide. Pour supprimer un dossier non vide il faudra donc lister l'ensemble des fichiers et dossiers et les supprimer avant de supprimer le dossier parent.

La fonction GetCurrentDirectory() retourne le répertoire courant. Elle peut être utilisée par exemple pour obtenir le chemin du processus courant. En effet, généralement, les programmes sont exécutés avec pour dossier courant, le dossier contenant l'exécutable. Ceci est toujours vrai sauf mention contraire de l'utilisateur. La récupération du chemin de l'exécutable courant par cette méthode n'est donc pas garantie mais elle marchera dans la plupart des cas.

La fonction GetTempPath() permet de récupérer le chemin du dossier temporaire (généralement Windows\temp). Les fonctions GetWindowsDirectory() et GetSystemDirectory() permettent de récupérer le chemin du dossier contenant Windows et du dossier système (system ou system32).

IV-G. Manipulations des chemins

IV-G-1. Cours théorique

Windows fournit une collection de fonctions permettant de travailler sur les fichiers, les dossiers ou les chemins. Voici une liste non exhaustive des fonctions les plus utilisées :

  • PathIsDirectory() permet de déterminer si le chemin spécifié est un dossier.
  • PathFileExist() détermine si le fichier spécifié existe.
  • PathCanonicalize() supprimer toutes les références symboliques du chemin et le convertit en chemin absolu. Ex. : (c:\a\.. sera converti en c:\).
  • PathCompactPath() permet de réduire la taille occupée par le chemin. Cette fonction remplace une partie de chemin par des points.
  • PathFindExtension() retourne l'extension du nom de fichier spécifié.
  • PathFindFileName() retourne le nom de fichier dans le chemin spécifié.
  • PathGetArgs() retourne les arguments du chemin spécifié.
  • PathRelativePathTo() permet de relativiser un chemin par rapport à un autre chemin.
  • PathRemoveArgs() supprime les arguments du chemin passé en paramètre.
  • PathRemoveExtension() supprime l'extension du chemin, si celle-ci existe.

Pour une liste exhaustive, reportez vous à l'annexe B.


Java et Eclipse tutoriel JM_Doudoux

Programmation avec l'API Windows

Télécharger le projet ... http://bob.developpez.com/tutapiwin/projets/
  • V. Le multithreading
    • V-A. Introduction
      • V-A-1. Cours théorique
    • V-B. Notion de processus et de thread
      • V-B-1. Cours théorique
    • V-C. Partage des tâches
      • V-C-1. Cours théorique
    • V-D. Synchronisation
      • V-D-1. Cours théorique
    • V-E. Premier exemple
      • V-E-1. Cours théorique
    • V-F. Arrêt d'un thread
      • V-F-1. Cours théorique
    • V-G. Récupération des messages
      • V-G-1. Cours théorique
    • V-H. Communication inter-threads
      • V-H-1. Cours théorique
    • V-I. Sections Critiques
      • V-I-1. Cours théorique
    • V-J. Fonctions d'attente
      • V-J-1. Cours théorique
    • V-K. Événements
      • V-K-1. Cours théorique
    • V-L. Sémaphores
      • V-L-1. Cours théorique
    • V-M. Trois projets simples
      • V-M-1. Cours théorique
    • V-N. Performances
      • V-N-1. Cours théorique

V. Le multithreading

V-A. Introduction

V-A-1. Cours théorique

Commençons tout d'abord par une explication sur le terme de multithreading. Le multithreading est le fait d'exécuter des tâches simultanément (multitâche). Les termes multitâche et multithread ne sont pas totalement similaires. Le terme multithread indique une application qui effectue différentes taches simultanément. Le terme de multitâche est un terme plus général et plus commun pour parler d'un système capable de gérer plusieurs applications simultanément.

Dans un programme classique, les instructions sont traitées de manière linéaire, "ligne à ligne". En ignorant le reste du système on pourrait dire que le processeur exécute les instructions de la ligne courante, puis passe à la suivante. En réalité ce n'est pas tout à fait vrai car les autres applications doivent elles aussi avoir accès au processeur, ainsi que le système d'exploitation lui-même.

Un thread est en fait une unité d'exécution. Chaque thread peut avoir accès au processeur et exécuter des instructions. Un thread peut se comporter exactement comme si il ne partageait pas le processeur (mais dans ce cas, il faudrait disposer d'autant de processeurs qu'il y a de threads). Pour éviter cet inconvénient, le système donne l'accès au processeur à chaque thread durant un temps très court (quelques millisecondes), et ceci de manière circulaire. Si le nombre de thread augmente, chaque thread devra attendre un délai plus long avant d'obtenir de nouveau l'accès au processeur.

De cette manière, chaque application peut fonctionner comme si elle était seule à utiliser le processeur. La seule différence est que l'exécution est plus lente. Si le processeur est suffisamment rapide, l'exécution d'un nombre modéré de threads peut s'effectuer très rapidement. C'est le cas actuellement. Dans un système utilisant Windows, le nombre de threads avoisine 50 au repos. Il dépasse rapidement 100.

L'utilisation du multithread consiste à créer un programme qui comporte plusieurs threads. Ce programme peut donc exécuter plusieurs instructions "simultanément". On peut alors se demander l'utilité d'une telle pratique. En effet, puisque le processeur est partagé, l'exécution du programme ne sera pas plus rapide. Elle sera seulement fragmentée. Un exemple simple permet de comprendre l'utilité du multithreading.

Considérons une application réalisant un transfert de fichier. Cette application dispose d'une interface graphique présentant une barre de progression ainsi qu'un bouton 'annuler'. En effet l'utilisateur désire être tenu au courant de l'avancement de la copie. De plus il veut pouvoir stopper celle-ci à tout moment si il le désire. Le programme doit donc réaliser une boucle de manière à recevoir les messages. Mais il doit également s'occuper de la copie des fichiers, ce qui reste son rôle principal. Si le programme écrit sur le disque, il ne peut plus recevoir les messages. La fenêtre ne sera donc plus rafraîchie. De plus, si l'utilisateur clique 'annuler', l'application ne traitera pas cette demande puisqu'elle ne recevra même pas le message… Une telle application utilisera donc 2 threads. Le premier thread s'occupera de la réception et du traitement des messages. Le deuxième thread s'occupera de la copie des fichiers. De cette manière, la réception des messages sera effectuée même au cours de la copie. Si l'utilisateur demande l'arrêt de la copie, le premier thread devra simplement stopper l'exécution du second. La copie sera alors stoppée.

V-B. Notion de processus et de thread

V-B-1. Cours théorique

Il ne faut pas confondre les processus et les threads. Un processus est une application. Tout processus dispose d'au moins un thread. Il peut cependant en posséder plusieurs. Pour l'utilisateur moyen, les threads sont invisibles. Ils sont masqués par les processus. La différence entre un processus et un thread est qu'un processus ne constitue en rien une unité d'exécution. L'exécution simultanée de plusieurs processus est réalisée grâce au multithreading. Le processus représente simplement un application vis-à-vis du système. Lorsque l'utilisateur demande la terminaison d'une application, tous les threads appartenant à cette application devront être terminés.

Tout processus doit comporter au moins un thread, de manière à exécuter le point d'entrée du programme. Ce thread ne pourra jamais être terminé sans que l'application ne soit elle-même terminée. Le processus ne doit en aucun cas être assimilé à ce thread. Le thread constitue simplement l'unité d'exécution de base du processus. Le système ne communiquera jamais avec le processus mais toujours avec l'un des threads de ce processus. En effet, le processus n'étant pas une unité d'exécution il ne réalise aucune tâche. Le premier thread est créé par le système. C'est pour cette raison que dans un programme comportant un seul thread, aucune référence n'est faite aux fonctions de l'API relatives aux threads. La création de nouveaux threads devra être explicite. C'est ce qui sera étudié tout au long de ce chapitre.

V-C. Partage des tâches

V-C-1. Cours théorique

Avant d'utiliser des threads, il est nécessaire de bien comprendre comment le système gère ces threads. En effet, un programme multithread mal conçu peut conduire à une très forte baisse des performances système.

Il faut bien comprendre que si le système d'exploitation accorde à chaque thread un temps d'accès au processeur, le thread n'est pas obligé d'utiliser totalement ce temps. Le système accorde simplement un temps maximum d'utilisation. Au-delà de cette période, le contrôle du processeur sera passé à un autre thread. Si le thread n'a plus aucune tâche à effectuer, il doit repasser lui-même le contrôle au système. De cette manière, une application inactive ne monopolisera pas inutilement le processeur. Si une application inactive n'effectue pas ce passage, elle utilisera totalement son quota d'accès. Le système affichera alors une charge processeur de 100% et les performances seront fortement ralenties.

Le système d'exploitation dispose également d'un système de priorités permettant de gérer les quotas d'accès alloués aux processus. Voici un résumé rapide du fonctionnement de ce système de priorité :

  • Time Critical : ce thread requiert l'utilisation totale du processeur. Les quotas alloués à ce thread sont infinis. Si un tel thread ne repasse pas spontanément le contrôle au reste du système, le système restera en attente. Le contrôle du processeur ne sera accordé à aucun autre thread. Tout le système sera alors bloqué jusqu'à ce que le thread repasse le contrôle. Ce type d'application n'est en réalité quasi jamais utilisé. Il doit être réservé à des utilisations très particulières (ex : un gestionnaire de souris).
  • High : ce thread recevra le contrôle du processeur en priorité. Un tel thread ne peut pas être bloquant pour le système mais il pourra ralentir énormément les performances. Un thread utilisant cette priorité et effectuant un accès permanent au processeur causera un effondrement des performances générales du système. Cette priorité ne doit généralement être accordé qu'à des applications effectuant des tâches très brèves ou étant en premier plan. Les performances de ce thread ne seront quasiment pas affectées par l'exécution d'autres applications.
  • Normal : ce thread a une priorité normale. Il recevra le processeur exactement autant de fois que les autres threads de la même priorité. Si deux threads utilisent cette priorité et utilisent le processeur en permanence, les performances de chacun des thread seront divisées par 2. La quasi totalité des threads utilise cette priorité (c'est d'ailleurs la plus recommandée hors cas très particuliers).
  • Idle : ce thread recevra le contrôle du processeur seulement si aucune autre application ne demande le contrôle. Si ce thread effectue des tâches en continu et que toutes les autres applications sont inactives, il disposera de l'accès processeur dans sa quasi totalité. Si un autre thread effectue des tâches continues, ce thread n'aura plus aucun accès au processeur. Ce type de priorité peut être accordé à des applications effectuant des tâches de fond. De cette manière les performances du système resteront inchangées. La charge du processeur sera alors continuellement de 100 %. Le partage sera alors : 0 % pour le thread Idle si le système utilise le processeur. 100 % pour le thread Idle si le système est inactif.

De manière générale il est inutile de modifier la priorité des threads. La modification des priorités ne doit être faite que dans les cas particuliers et en connaissance de cause. En particulier, les priorités Time Critical et High ne doivent jamais être utilisées abusivement.

De manière à conserver des performances système optimales, un thread doit repasser le contrôle au système dès qu'il devient inactif. Pour cela il dispose de plusieurs méthodes. Prenons l'exemple d'un thread qui désire attendre 150ms. Si ce thread effectue une boucle en testant l'heure système, il utilisera totalement son quota et le système affichera une charge de 100 %. Si eu lieu de cela le thread effectue un appel à la fonction Sleep(), il repassera le contrôle au reste du système pour une durée de 150ms. Le contrôle du processeur ne lui sera plus accordé durant cette durée. La charge processeur sera alors de 0 %. Le thread sera alors en attente. De cette manière les autres threads auront accès au processeur comme ci ce thread n'était pas présent et les performances système seront conservées.

Un autre exemple est la récupération des messages. La fonction GetMessage() repasse le contrôle au système dès que tous les messages ont été traités. Si l'application est inactive, le contrôle est donc repassé immédiatement au système et la charge processeur affichée est de 0 %. Si un thread utilise PeekMessage() dans une boucle, le thread conservera l'accès au processeur même si aucun message n'est présent dans la file d'attente. La charge processeur sera donc de 100 % même si l'application est inactive. Ce type d'erreurs ne doit donc surtout pas être commis sous peine de diminuer gravement les performances globales du système.

Il est également indispensable de comprendre qu'on ne peut jamais prévoir l'ordre dans lequel les threads recevront l'accès au processeur. Une application multithread devra toujours utiliser les fonctions de synchronisation (qui seront détaillées plus tard) et ne devra jamais tenter de prévoir l'ordre d'exécution des instructions. De plus, deux lignes consécutives d'une fonction ne seront pas forcément effectuées à la suite car le contrôle du processeur pourra être passé à d'autres threads entre temps. Il faut donc bien avoir en tête les contraintes imposées par un programme multithread avant de se lancer dans sa réalisation.

V-D. Synchronisation

V-D-1. Cours théorique

Les fonctions de synchronisation sont indispensables dans un programme multithread. La mauvaise synchronisation d'une application peut conduire à des plantages ou à des performances totalement désastreuses.

Étudions l'exemple d'un processus contenant deux threads, chacun utilisant le même tableau global. Un tel programme peut donner des résultats surprenant dans le cas d'une mauvaise synchronisation. Par exemple, deux évaluations consécutives de la même variable pourront retourner des valeurs différentes. Ce genre de comportement n'est généralement pas souhaité. Les deux threads devront alors se synchroniser avant d'accéder aux ressources partagées. Dans ce cas, la synchronisation consistera en une exclusion mutuelle (d'autres types de synchronisation seront étudiées plus tard). L'exclusion mutuelle consiste à faire patienter tous les threads demandant l'accès aux données partagées tant que le thread les utilisant n'a pas déclaré qu'il avait terminé. Chaque thread utilise une fonction pour signaler qu'il entre dans une section protégée. Il utilisera ensuite une seconde fonction pour signaler qu'il sort de la section protégée. Si un autre thread demande à entrer dans un bloc protégé, il sera mis en attente. Le thread qui est entré dans la section protégée doit impérativement signaler sa sortie de la section protégée sinon les autres threads resteront en attente. L'accès aux données partagées doit donc être aussi bref que possible.

Les fonctions de synchronisation sont un très bon moyen de mettre des threads en attente tout en conservant une charge processeur nulle. Si un thread entre dans une phase d'attente, son utilisation processeur restera très faible durant toute la période d'attente. Le système lui accordera de nouveau le contrôle dès que cela sera possible.

La synchronisation des threads devra donc toujours être effectuée par des fonctions de l'API et jamais par des boucles. L'utilisation de boucles ne repassant pas le contrôle au système rendrait les performances désastreuses et ralentirait l'exécution du processus lui-même car l'accès du processeur serait accordé à des threads en attente, ce qui ne présente aucun intérêt.

V-E. Premier exemple

V-E-1. Cours théorique

Ce premier exemple montre comment créer un thread. Le point d'entrée du thread peut être n'importe quel fonction respectant un certain typage. Dès la fonction constituant le point d'entrée retourne, le thread est terminé par le système.

La fonction CreateThread() qui démarre le thread retourne un HANDLE ainsi qu'un identifiant. L'identifiant est une valeur de type DWORD. Dans cet exemple, le HANDLE et l'identifiant ne nous seront d'aucune utilité car le thread se terminera spontanément. Le HANDLE ne sera donc même pas récupéré. Il est indispensable de passer un pointeur non nul pour l'identifiant. Dans le cas contraire, la fonction CreateThread() échouera. L'identifiant ou le HANDLE sont utilisés pour communiquer avec le thread.

Notons que l'ensemble des variables globales est accessible à la procédure du thread.

Télécharger le projet commenté.

V-F. Arrêt d'un thread

V-F-1. Cours théorique

Le problème de l'arrêt d'un thread est délicat. Un thread peut s'arrêter spontanément en terminant sa fonction principale. Dans ce cas, on considérera que l'arrêt était prévu. Dans le cas où l'application doit se terminer, l'arrêt ne peut pas être prévu. Dans ce cas, il existe différentes méthodes de terminer les threads en cours.

Voici tout d'abord la méthode la plus évidente, et aussi la plus mauvaise. La fonction TerminateThread() fournie par l'API permet de terminer un thread. Le thread est alors immédiatement stoppé. Bien que cette fonction soit fournie en standard avec l'API Windows, elle ne sera en pratique quasiment jamais utilisée. Son utilisation doit être réservée à des cas très particuliers et en connaissance de cause. Comme ces cas sont généralement très rares on peut considérer que dans un programme bien conçu, cette fonction ne devra jamais être utilisée. En effet, cette fonction termine le thread courant sans appeler les fonctions de désallocation de mémoire. Ceci est dû au fait qu'il n'existe pas de point de sortie prédéfini à un thread. La fonction TerminateThread() ne peut donc pas effectuer la désallocation de mémoire. Toute la mémoire allouée de manière dynamique sera donc perdue. De même, les dlls chargées par le thread ne seront pas libérées… Cette fonction ne doit donc être utilisée que dans le cas d'un thread n'utilisant que des allocations de mémoire statique, et aucune ressource extérieure…

Pour éviter l'utilisation de cette fonction, chaque thread doit être muni d'un ou de plusieurs points de sortie. Les systèmes de communication inter-threads seront utilisés pour mener le thread vers ce point de sortie. De cette manière la désallocation de la mémoire ainsi que la libération des ressources extérieures pourra être effectuée. La communication inter-threads sera traitée plus tard. On peut tout de même citer les variables globales et les messages qui sont les principaux moyens de communication entre threads. Bien que plus complexes à mettre en place, les méthodes de communication inter-threads seront donc toujours à préférer.

L'arrêt de l'application entraîne automatiquement l'arrêt de l'ensemble des threads. Le système appellera alors la fonction TerminateThread() pour arrêter les threads encore actifs. Une application doit donc toujours s'assurer de l'arrêt de l'ensemble des threads avant de se terminer. Les fonctions de synchronisation sont très adaptées à ce type de tâche, comme la fonction WaitForMultipleObjects() qui sera vue en détails plus tard.

V-G. Récupération des messages

V-G-1. Cours théorique

La récupération des messages peut avoir deux fonctions : récupérer les messages de communication liés à une fenêtre ou récupérer les messages de communication inter-thread. La réception des messages doit être effectuée de manière différente selon le type de messages. De manière générale, les messages associés à des fenêtres doivent être traités avec la fonction GetMessage() dans une boucle. Cette fonction permet de passer le contrôle aux autres threads du système dès que la file d'attente est vide. De cette manière les performances sont optimales. De plus, les messages associés aux fenêtres doivent être traités rapidement. L'utilisation de cette fonction dans une boucle permet donc un traitement rapide tout en conservant de bonnes performances.

Le traitement des messages dans le cadre de la communication inter-thread est différent (il sera vu plus en détail plus tard). Il concerne par exemple l'envoi d'un message d'abandon de la tâche courante. La fonction GetMessage() est très peu adaptée au traitement de ce type de messages car elle est bloquante. Le thread sera donc suspendu tant que la file d'attente sera vide. Un thread ne s'occupant pas de la réception des messages associés à une fenêtre a généralement une tâche bien précise à exécuter. Il serait donc absurde d'utiliser une fonction bloquante pour le traitement des messages. De plus, ces messages ne nécessitent pas un traitement rapide. On préférera donc utiliser la fonction PeekMessage() qui elle n'est pas bloquante. Il faudra donc utiliser cette fonction dans une boucle. Comme l'utilisation de cette fonction dans une boucle provoque de graves problèmes de performances, il ne faudra pas effectuer cette boucle trop fréquemment. Prenons l'exemple d'un thread gérant un transfert de fichier. Le transfert de fichier est réalisé par une boucle qui lit les données puis les écrit à une place différente. Selon la durée moyenne d'une boucle, on déterminera un intervalle d'appel de PeekMessage(). Par exemple, toutes les 100 itérations de la boucle principale, la fonction PeekMessage() sera appelée dans une boucle jusqu'à épuisement de la fille d'attente. Ce type de traitement est adapté à un faible nombre de messages, de manière à ne pas ralentir le travail de la boucle principale. En supposant que la durée moyenne d'une itération de la boucle soit de 5ms, les messages seront donc traités 2 fois par seconde. Si un message demandant l'arrêt de la tâche en cours est envoyé, il sera traité au plus 500ms plus tard, ce qui est très acceptable. De plus, cette technique ne nuit que très peu aux performances de la boucle principale puisque seulement une itération sur 100 sera sensiblement ralentie.

Si la rapidité du traitement des messages n'a que peu d'importance on pourra espacer les appels à PeekMessage() de manière à conserver autant que possible les performances de la boucle principale. Cette fonction sera donc particulièrement adaptée au traitement de messages de notifications envoyés à des thread effectuant une tâche précise.

Dans le cas d'un thread effectuant des tâches 'sur commande' on préférera bien entendu la fonction bloquante GetMessage() puisque le thread sera en attente de tâche (et ne consommera donc pas de ressources).

V-H. Communication inter-threads

V-H-1. Cours théorique

La communication inter-threads permet aux différents threads d'une application de dialoguer entre eux. Comme la réception des messages peut poser problème, on tentera généralement de réduire au minimum la communication inter-threads et donc de réaliser des threads aussi indépendants que possible.

Outre les fonctions de synchronisation qui ne sont pas à proprement parler des fonctions de communication, il existe deux moyens principaux de communication : les variables globales et les messages. Les variables globales sont un moyen de communication facile à mettre en place. Toutefois, leur utilisation devra être faite avec rigueur pour éviter des problèmes dus à l'accès de variables partagées. De plus dans certains cas, l'utilisation de variables globales peut nuire aux performances.

Une variable globale ne doit jamais être utilisée comme moyen de synchronisation. En effet, il est possible de placer la valeur d'une variable globale à 0 et d'effectuer une boucle avec un test sur cette variable de manière à attendre tant que la valeur reste à 0. Un second thread peut alors débloquer l'attente en modifiant la valeur de cette variable globale. Bien que fonctionnant, cette méthode ne devra jamais être utilisée car elle provoque une forte charge processeur inutile. Dans ce cas, les fonctions de synchronisation devront être utilisées car elles permettent de laisser l'accès processeur au reste du système tant que le thread est en attente.

De plus, deux threads ne doivent jamais accéder en écriture à une même variable globale. Ce type d'accès peut provoquer des plantages dus à la manière dont les variables globales sont utilisées (ces variables sont parfois placées dans des registres pour optimisation). L'utilisation de variables globales pour la communication inter-threads implique une communication à sens unique. Un thread accède à cette variable en écriture et les autres threads réagissent en fonction de cette valeur. Une telle utilisation ne posera pas de problèmes. Le partage de données mémoire en écriture implique des précautions particulières comme la synchronisation.

Les variables globales pourront donc être utilisées comme drapeau. Par exemple pour signifier à un thread qu'il doit se terminer. Si le thread effectue une boucle, on peut placer dans cette boucle un test sur la variable globale. Le thread principal place cette variable à une valeur principale au moment de quitter, ce qui provoque l'arrêt des autres threads. Notons tout de même que le thread principale devra attendre que l'ensemble des threads ait terminé avant de stopper l'application. En effet, l'arrêt de l'application provoque une fermeture de tous les threads en cours (ce qui revient à un appel de TerminateThread()). Il faudra donc placer un système d'attente dans le thread principal (fonctionnant via d'autres variables globales ou par un système de messages de notification).

L'utilisation de variables globales est donc une méthode rapide de communication. Toutefois cette méthode est soumise à un certain nombre de contraintes qui ne doivent surtout pas être ignorées.

Les messages constituent la deuxième méthode de communication. La fonction PostThreadMessage() permet d'envoyer des messages à un thread, même si celui-ci ne gère pas de fenêtres. Le thread pourra alors utiliser les fonctions GetMessage() ou PeekMessage() pour récupérer les messages. Bien entendu, il sera inutile d'utiliser les fonctions TranslateMessage() et DispatchMessage() puisque ces messages ne sont pas destinés à des fenêtres.

Il existe plusieurs méthodes pour créer des messages personnalisés. Tout d'abord, il est possible de définir une constante au niveau du préprocesseur (#define). Les valeurs valides pour les messages définis par l'utilisateur doivent être compris entre les valeurs WM_USER et 0x7FFF. La constante WM_USER est définie dans les headers Windows.

La deuxième méthode consiste à utiliser la fonction RegisterWindowMessage(). Cette fonction retourne une valeur utilisable pour un message en fonction d'une chaîne de caractère. Le système garantit l'unicité du message en fonction de la chaîne de caractère. Si deux applications distinctes utilisent cette fonction avec la même chaîne, elles obtiendront la même valeur. Cette fonction doit donc seulement être utilisée dans le cadre de la communication inter-applications, c'est à dire au niveau système. Si les messages sont locaux à l'application, il est recommandé de définir des constantes.

Si un thread désire communiquer une plus grande quantité d'information, il peut joindre au message 2 valeurs (lParam et wParam). Deux choix sont alors possibles. Utiliser ces valeurs comme paramètres ou joindre un pointeur. La méthode à utiliser pour joindre un pointeur est la suivante : le thread qui poste le message alloue dynamiquement de la mémoire et poste le pointeur. Le thread émetteur ne doit pas désallouer cette mémoire. C'est le thread récepteur qui s'occupera de la désallocation de la mémoire. Cette méthode assure que le pointeur sera toujours valide à la réception du message.

V-I. Sections Critiques

V-I-1. Cours théorique

Les sections critiques sont utilisées dans le cadre de la synchronisation, de manière à réaliser des exclusions mutuelles. Elles permettent de créer une section de code qui sera protégé, c'est à dire qu'un seul thread à la fois aura accès à ce code. Les sections critiques sont très utiles pour protéger des données partagées.

L'utilisation des sections critiques est relativement simple. Pour créer une section critique, l'application doit déclarer une variable de type CRITICAL_SECTION, généralement globale. Puis au moins un thread doit initialiser la section critique en appelant la fonction InitializeCriticalSection(). Cette fonction peut être appelée un nombre indéfini de fois. Par exemple, chaque thread qui va utiliser la section critique peut s'assurer qu'elle est valide en appelant cette fonction.

Deux fonctions seront ensuite utilisées pour signifier l'entrée et la sortie d'un bloc protégé. La fonction EnterCriticalSection() permet de signaler l'entrée dans un bloc protégé. Si un thread a déjà appelé cette fonction et n'est pas encore sorti du bloc protégé, cette fonction mettra en attente n'importe quel autre thread. Le même thread peut appeler cette fonction plusieurs fois sans être bloqué, mais il devra alors appeler la fonction de sortie de bloc un nombre de fois égal pour indiquer sa sortie du bloc protégé.

Un thread indique qu'il sort d'un bloc protégé en appelant la fonction LeaveCriticalSection(). Un des threads en attente sera alors débloqué et pourra à son tour exécuter le bloc protégé.

V-J. Fonctions d'attente

V-J-1. Cours théorique

Les fonctions d'attente permettent d'attendre de manière explicite qu'un événement se produise. Les fonctions d'attente sont WaitForSingleObject() et WaitForMultipleObjects(). Ces fonctions permettent d'attendre que l'état d'un objet change. Le thread appelant une de ces fonctions peut définir un temps maximal d'attente (qui sera éventuellement infini).

Différents objets peuvent être utilisés. Par exemple, la fonction WaitForSingleObject() peut être utilisée avec le HANDLE d'un thread. La fonction attendra alors que le thread se termine pour retourner. Pour attendre que plusieurs threads se terminent, une application pourra alors appeler la fonction WaitForMultipleObjects() en spécifiant qu'elle désire attendre que l'ensemble des threads ait terminé leur exécution. Ces fonctions mettent le thread qui les appelle en attente et sont donc excellentes au niveau performance. Leur utilisation est donc hautement recommandée.

Un événement créé explicitement peut également être utilisé avec ces fonctions. Ce cas sera étudié dans la partie consacrée aux événements. De même, le HANDLE d'un processus peut être utilisé de manière à attendre qu'il se termine.

Dans le cas de la communication inter-threads nous avons vu qu'une application pouvait utiliser une variable globale pour spécifier à ses threads qu'ils devaient se terminer. L'application doit ensuite attendre que les threads se terminent avant de quitter. Cette attente sera typiquement réalisée par un appel à la fonction WaitForMultipleObjects(). Lorsque cette fonction retournera, l'application sera alors assurée que l'ensemble de ses threads auront terminés et pourra quitter sans risque.

V-K. Événements

V-K-1. Cours théorique

Les événements peuvent être utilisés dans les fonctions d'attente. L'API Windows fournit des fonctions permettant de créer des événements de manière explicite. L'état de ces événements pourra ensuite être modifié de manière explicite. L'état d'un événement peut être signalé ou non signalé. Lorsqu'un événement est en état non signalé, l'appel à une fonction d'attente provoquera la suspension du thread appelant jusqu'à ce que l'événement passe en état signalé.

Les fonctions CreateEvent() et CloseHandle() peuvent être utilisées pour la création et la destruction d'événements. Les fonctions SetEvent(), ResetEvent() et PulseEvent() pourront ensuite être utilisées pour modifier l'état de l'objet.

Les événements peuvent être configurés de différentes manières. La configuration d'un événement se fait lors de sa création. Si l'objet est en mode 'réinitialisation manuelle' il restera en état signalé tant que son état ne sera pas explicitement réinitialisé grâce à la fonction ResetEvent(). Si plusieurs threads étaient en attente de cet objet, ils seront alors tous libérés au moment où l'état de l'objet passera à 'signalé'.

Si l'objet est en mode 'réinitialisation automatique', son état sera automatiquement replacé à 'non signalé' dès qu'un thread en attente sera libéré. Si plusieurs threads sont en attente d'un même événement, alors un seul thread sera libéré eu moment ou l'état sera passé à 'signalé'.

Les événements sont un moyen pratique de synchroniser plusieurs threads de manière explicite.

Les événements peuvent par exemple être utilisés pour mettre un thread en attente tandis qu'un autre thread initialise des données. Dès que les données seront initialisées, le ou les threads seront débloqués par le thread ayant initialisé les données.

Tout comme les sémaphores, les événements peuvent être nommés ou non. Si l'événement est nommé, son nom doit être unique dans l'ensemble du système. Si une autre application tente de créer un événement du même nom, la fonction CreateEvent() retournera un HANDLE sur l'événement précédemment créé. Ceci peut être utilisé pour synchroniser des applications entre elles.

V-L. Sémaphores

V-L-1. Cours théorique

Les sémaphores sont utilisés pour limiter le nombre de threads en fonctionnement. Prenons le cas d'un serveur. On désire limiter le nombre maximal de connections simultanées. Dans ce cas, on utilisera la synchronisation fournie par les sémaphores.

Pour utiliser un sémaphore, une application doit créer un objet de type sémaphore en appelant la fonction CreateSemaphore(). Un sémaphore est constitué d'un compteur. Le statut d'un sémaphore est signalé lorsque le compteur est supérieur à 0 et non signalé lorsque le compteur est à 0. Lors de la création du sémaphore, on spécifie la valeur initiale du compteur, ainsi que sa valeur maximale. La valeur maximale du compteur correspond au nombre maximum de threads qui pourront être exécutés simultanément.

Chaque thread désirant se synchroniser doit utiliser une fonction d'attente (WaitForSingleObject() par exemple). Lorsqu'un thread appelant une fonction d'attente est libéré, le compteur du sémaphore est décrémenté de 1. Dès que la valeur du compteur atteint 0, les threads appelant les fonctions d'attente seront bloqués. Lorsqu'un thread sort d'un bloc synchronisé, il doit appeler la fonction ReleaseSemaphore(). Cette fonction incrémente de 1 le compteur du sémaphore, permettant ainsi à un autre thread d'être débloqué.

Le compteur du sémaphore ne pourra jamais dépasser la valeur maximale spécifiée au début. De cette manière, on peut très facilement limiter le nombre de threads effectuant une tâche spécifiée, et conserver ainsi les performances système. Une augmentation trop importante du nombre de threads en exécution simultanée poserait en effet des problèmes de performances.

Il est possible de spécifier un nom lors de la création du sémaphore. De cette manière, des applications différentes peuvent se synchroniser. La première crée un sémaphore nommé. La seconde application tente ensuite de créer un sémaphore du même nom. Ceci aura pour effet de lui retourner un HANDLE sur le sémaphore déjà existant. Le nom d'un sémaphore doit donc être unique dans l'ensemble du système. Remarquons qu'un sémaphore ne doit pas nécessairement être nommé.

V-M. Trois projets simples

V-M-1. Cours théorique

Voici trois projets présentant l'utilisation des fonctions de synchronisation étudiées : sections critiques, événements et sémaphores. Ces trois projets ont pour but d'expliquer l'utilité des différents types de synchronisation. De plus, ils pourront aider à comprendre la mise en place d'une synchronisation rudimentaire.

Il ne faut jamais oublier que les accès à la console Windows doivent être synchronisés de manière à éviter des résultats totalement imprévisibles. Une section critique est parfaitement adaptée pour résoudre ce genre de problèmes.

Télécharger les projets commentés :Projet 09 - Projet 10 - Projet 11 .

V-N. Performances

V-N-1. Cours théorique

La création d'applications multithread est généralement très performante. Toutefois, il faudra faire attention à préserver les performances systèmes. La synchronisation devra toujours être effectuées grâce aux objets fournis par l'API comme les sections critiques, les événements ou encore les sémaphores. Il faut toujours penser qu'un thread inactif doit laisser le contrôle au système et non pas effectuer une boucle. Bien qu'elles fonctionnent parfaitement, les boucles sont désastreuses au niveau performances système et on perd rapidement l'intérêt de l'application multithread.

Il ne faut pas non plus tenter de segmenter à tout prix les tâches. Un nombre trop important de threads peut nuire aux performances système. Le travail qui peut être effectué par un thread ne doit donc pas être confié à 2 threads.

Les applications clients réseau sont souvent très performantes grâce au multithreading. En effet, le fait d'établir plusieurs connections avec la machine hôte permet d'améliorer le débit réseau. Toutefois, un nombre trop important de threads finira par nuire aux performances de l'application car le système perd du temps en passant le contrôle du processeur à un nombre trop important de threads. Les applications sont alors considérablement ralenties.

De plus, la réalisation d'applications multithread est souvent source d'erreurs. En effet, il ne faut jamais oublier que des instructions situées dans des threads seront exécutées dans un ordre quelconque. Il ne faut jamais tenter de prévoir l'ordre d'exécution. A partir du moment où l'ordre d'exécution n'est pas certain, il faudra impérativement utiliser les fonctions de synchronisation.

Il faut également comprendre que le fait d'utiliser des fonctions de synchronisation nuit aux performances de l'application. Les fonctions de synchronisation ne doivent donc pas être utilisées si elles ne sont pas nécessaires. De plus, si un nombre important de threads partage des données, le temps d'attente pour obtenir l'accès à ces données va rapidement devenir important. Chaque thread passera donc un temps important à attendre, ce qui est d'autant plus problématique si la tâche qu'il doit effectuer est courte.

Il faut également éviter impérativement des entrées - sorties successives dans des sections critiques. En effet, si le temps d'exécution entre les sections critiques est trop court, le thread aura très rapidement un temps d'attente supérieur à son temps de calcul. Ce problème se manifestera encore plus avec un nombre important de threads. Si un thread doit faire des accès successifs à des données partagées, il peut copier ces données dans un espace non partagé ou rester dans la section critique le temps d'exécuter sa tâche. Si le temps d'exécution de la tâche est court, un thread aura tout intérêt à ne pas sortir de la section critique.

Il faut donc impérativement prendre en compte les questions de performance lors de la réalisation d'applications multithread. De plus, les plantages dus à des problèmes de partage de données ne surviendront pas forcément lors des tests car les threads ne s'exécutent jamais de la même manière. Il faut donc prévoir les problèmes de partage et non pas compter sur les tests pour les déceler.


Java et Eclipse tutoriel JM_Doudoux

Programmation avec l'API Windows

Télécharger le projet ... http://bob.developpez.com/tutapiwin/projets/
  • VI. Annexes
    • VI-A. Annexe A : Les contrôles
      • VI-A-1. Styles des contrôles
      • VI-A-2. Messages utilisables avec les contrôles
    • VI-B. Annexe B : Fonctions par thèmes

VI. Annexes

VI-A. Annexe A : Les contrôles

Vous pourrez trouver ici des extraits de l'aide fournie par Microsoft sur les styles des différents contrôles. Ces extraits sont fournis tels quels, comme vous pourriez les trouver sur msdn.microsoft.com. Certains ont été remis en page.

VI-A-1. Styles des contrôles

Edit Styles
Button Styles
List Box Styles
Combo Box Styles
Progress Bar Styles

Bitmap Functions
Coordinate Space and Transformation Functions
Cursor Functions
Device Context Functions
Dialog Box Functions
File Functions
Font and Text Functions
Keyboard Input Functions
Line and Curve Functions
Menu Functions
Message and Message Queue Functions
Painting and Drawing Functions
Path Functions
Resource Functions
Timer Functions
Window Functions

VI-A-2. Messages utilisables avec les contrôles

List Box
Progress Bar
Combo Box

VI-B. Annexe B : Fonctions par thèmes

Vous pourrez trouver ici des extraits de l'aide fournie par Microsoft. Ces extraits sont fournis tels quels, comme vous pourriez les trouver sur msdn.microsoft.com. Certains ont été remis en page.

Bitmap Functions
Coordinate Space and Transformation Functions
Cursor Functions
Device Context Functions
Dialog Box Functions
File Functions
Font and Text Functions
Keyboard Input Functions
Line and Curve Functions
Menu Functions
Message and Message Queue Functions
Painting and Drawing Functions
Path Functions
Resource Functions
Timer Functions
Window Functions


D:\Com_CHM\Documentation CHM\_API\_API Win32 Tutoriel\Winapi3\WINAPI3.RTF

+$!#KWinAPI 3: More WinAPI Calls As Custom Help Macros

Vu dans ... Winapi3.hlp 632_Ko et Winapi2.hlp 81_Ko des années 94 conv. via Pedump.exe 180_Ko

Welcome to WinAPI 3, the Help file chock full of information on how to use Windows Programming API calls as custom Help macros. WinAPI 3 is the second such file (WinAPI 2 was the first) to go into explicit “how-to” information on how to use these API calls in your Help files. Select About WinAPI 3 from the Table of Contents window for more information and background.

WinAPI 3 is compiled, written, and presented as a free service and aid to other WinHelp authors by Paul Arnote, the author of MiniHelp Plus v3.21:MHP321 The COMPLETE Windows Help Authoring Utility and EW256BMP.DLL:EW256BMP 256 Color WinHelp Extension DLL v2.0.

Pass WinAPI 3 around to other WinHelp authors you contact or know. Upload it to any BBSs and major online services you might frequently visit. In other words, distribute this file freely. My goal is to make this like a little “newletter” published on an “as necessary” basis (also interpret as “when enough information has been gathered to necessitate another issue” publishing schedule). With this newsletter in WinHelp format, we can create a “clearing-house” of WinHelp information, demonstrating new techniques and custom macros that are available to every WinHelp author.

AND, I want to hear from you as well. The next “issue” of WinAPI will arrive much sooner if those of you who know other WinHelp “special effects” would share them with the rest of us. So, if you have any special tips, tricks, or discoveries about Windows Help, please fill out and mail the WinHelp Report FormReport to the address on the form and it will be included in the next “issue” of WinAPI with credit given where it is due.
Table Of Contents
  • Display Simple Message Boxes MSG
  • 256 Color BitmapsWITHOUT A DLL!
  • Playing WAV files in WinHelp
  • Other Devices in WinHelp
  • Playing AVI Video Files in WinHelp
  • Creating non-sizable WinHelp windows
  • Jumps to secondarywindows from Search
  • Using the “C” #include directive
  • Modifying / Deleting WinHelp’s Menus
  • Extended Characters in Windows Help
  • Internet sources for WinHelp info
  • More WinHelp Unsolved Mysteries
  • About MiniHelp Plus v3.21
  • About EW256BMP.dll
  • About WinAPI 3
  • Help Report Form
  • Order Form For Mini Help Plus & EW256Bmp
    • Message Boxes

    You can use, limitely, the Windows API MessageBox function to display messages to your users that require their response before continuing.
    The limitations exist in that you cannot trap the response the user makes if they are presented with more than one button to choose. Regardless of what button is pressed, Windows Help takes the “pushing” of a button as the signal to continue. For this reason, only those parameters of MessageBox that are of use to Help writers is presented below. If you wish to do more with MessageBox than what is allowed here, you will need to create a custom macro DLL.

    The MessageBox function is defined in Windows as follows:

    int MessageBox(hwndPa    rent, lpszText, lpszTitle, fuStyle)
    HWND hwndParent; /*handle of parent window*/
    LPCSTR lpszText; /*address of text in message box*/
    LPCSTR lpszTitle; /*address of title of message box*/
    UINT fuStyle; /*style of message box*/

    The MessageBox functi    on creates, displays, and operates a message-box window. The message box contains an application-defined message and title, plus any combination of the predefined icons. The only push button available to Help authors is the “OK” button, described in the fuStyle parameter.

    Parameters:

    hwndParent Identifies the parent window of the message box to be created. If this parameter is NULL, the message box will have no parent window. Help writers will most likely pass hwndContext as this parameter. You may also pass 0 (zero).

    lpszText Points to a null-terminated string containing the message to be displayed. Help writers will pass this parameter as a string of text, enclosed in single quotes, that defines the text of the message to appear in the body of the Message Box.

    lpszTitle Points to a null-terminated string to be used for the dialog box title. If this parameter is NULL, the default title Error is used. Help writers will want to pass along the text they want to display on the title bar of the message box as a string of text, enclosed in single quotes.

    fuStyle Specifies the contents and behavior of the dialog box. This parameter can be a combination of the following values:

    Buttons: Since Help writers can only use the 3OK” button (value is 0x0000), the other button values are not presented here.

    Icons:
    MB_ICONASTERISK, MB_ICONINFORMATION An icon consisting of a lowercase letter “I” in a circle appears in the message box. Value is 0x0040.

    MB_ICONEXCLAMATION An exclamation-point icon appears in the message box. Value is 0x0030.

    MB_ICONHAND, MB_ICONSTOP A stop-sign icon appears in the message box. Value is 0x0010.

    MB_ICONQUESTION A question-mark icon appears in the message box. Value is 0x0020.

    If you don't want one of the system icons in your message box, simply pass along 0x000 as the value for fuStyle.

    Returns:
    From what I can tell, Windows Help ignores the return value when executing this Windows API function.

    RegisterRoutine Format:
    RegisterRoutine(`use    r.exe',`MessageBox',`USSu')

    Macro Format:
    MessageBox(0,`text for body of message box',`text for title of message box',fuStyle value)
    MessageBox(hwndContext,`Howya like this message?!',`My Message',0x0020)

    The value for “hwnd” will be hwndContext for the currently displayed topic, or hwndApp for Help itself. Typically, you will use hwndContext as the window handle when calling the macro from within your Help file. You will most likely use hwndApp when calling the macro when the Help file is loaded by placing it in the [CONFIG] section of the HPJ file, or by using the MiniHelp PlusMHP321 .hpjconfig command.

    Limitations:
    As with all Help macros, the maximum length is limited to a total of 254 characters or less.

    Programmers, and Help writers, will want to note that you force a line break within the message in a very different way than when using this function in a program, or when forcing a line of text in the Help file to another line. You must place an ASCII linefeed character (\'0A) at the spot where you want the line to break and this counts as one of the characters in your macro limit of 254 characters or less. Here is an example:

    MessageBox(hwndContext,`You must first wash you hands.\'0A\'0AIf you do not wash your hands,\'0Ayour keyboard will get dirty.',`Cleanliness...Godliness',0x0010)

    This only works whe    n the MessageBox macro is invoked from within a topic file. I can find no line-breaking command for use when this macro is executed from the [CONFIG] section of the HPJ file. Therefore, your choices when executing this macro from the HPJ file (versus from the topic file) are to either limit your messages to a single line or let the line breaks occur where they may.

    Examples:

    {bmc bm18.BMP}! Messa
    geBox(hwndContext,`You must first wash you hands.

    If you do not wash your hands,
    your keyboard will get dirty.',`Cleanliness...Godliness',0x0010)

    Select the graphic ab
    ove to see an example of executing a simple message box from Windows Help. This example is the exact example cited in the above section talking about line breaks.

    +$#K256 Color Bitmaps

    WIT    HOUT A DLL!!??

    I stumbled across thi
    s quite by accident. The InternetInternet WinHelp newsgroup I frequently read and contribute to mentioned a Help fileOZ8PUO containing a full database of posts to the newsgroup. I went to the Usenet site the post mentioned and downloaded the entire 7.67 Megabyte Help file. I spent at least a couple of days going through this monstrous collection. And I must tell you, there is a wealth of information in there if you can dig it out.

    According to the posting, a gentleman by the name of Bruce Hartford on CompuServe discovered a way to display a 256 color bitmap in WinHelp without the control of an external DLL. I am going to relay that information to you here. Be forewarned, however, as this will be a rather lengthly topic since while the process itself is relatively simple, it is packed with potholes along the way if you don't follow a very narrow and precise path.

    First, you will need a few additional programs to convert to a 256 color format that WinHelp is capable of displaying. Here is a list of programs you will find useful/necessary in order to achieve this. All the programs listed are either widely available as shareware on most major online services and BBSs or are already included with Windows. Those programs with one asterick after them are required and those with two astericks after them are optional but recommended.

    You will need:

    PixFolio v2.0.156 Shareware *
    Paint Shop Pro v2.01     Shareware
    Windows Paintbrush C    ome with Windows *
    Hot Spot Editor Ava    ilable from Microsoft ftp site.Internet **
    Microsof    t VidEdit On Microsoft Jumpstart CD

    The secret to gettin    g WinHelp to display your “bitmap” in 256 colors is to not display it as a bitmapped image at all, rather as a Windows Metafile (WMF). Using PixFolio alone, you can convert your bitmapped image to an equivalent WMF. All you need to do is load in your bitmap and save it as a WMF. This resultant WMF graphic can then be used by WinHelp as is, with no further processing...but only so long as you display just one 256 color bitmap per Help topic. This is one of the “potholes” to which I referred earlier. Later, below, I will tell you how to work around this limitation.

    The test for whether or not WinHelp is capable of displaying a 256 color graphic file is to load it into the Hot Spot Editor (SHED.EXE). If the Hot Spot Editor can display the image, then WinHelp can also display it. To insure that the conversion occurred properly, load your newly created WMF into the Hot Spot Editor. If it looks alright, it will look equally good displayed in WinHelp.

    The above simple instructions are the very basics needed to display a 256 color graphic file in WinHelp without the control of an external DLL. Attempting to display more than one 256 color graphic file per Help topic, however, presents a whole other problem.

    DISPLAYING TWO OR MOR
    E 256 COLOR GRAPHICS: CONFLICTING PALETTES

    Attempting to displa    y
    two or more 256 color graphic files at a time in the same Help topic presents unique problems. When the first graphic is displayed, its palette is realized into the current window’s display context. Any other 256 color graphics displayed in the same window must derive its colors from the palette realized by the first graphic. Often times, this will cause the colors of the second and subsequent graphics to appear corrupted, since they must use the closest color match in the first graphic’s palette.

    But don’t despair. There is hope. The solution requires just a little more work on your part. First, load your 256 color bitmap into a graphics conversion program, such as Paint Shop Pro. Reduce the number of colors in the file to 236 or less. This accommodates the 256 color palette that Windows uses, of which it reserves 20 of those colors for its own use. Do this regardless of the number of colors reported by the graphic conversion program’s color count feature. Save the new resulting bitmapped image file and repeat this process for each bitmap you want to display in the same Help topic.

    Second, after completing the above conversion of the palettes, open up Windows Paintbrush and set the Image Attributes Image Size to at least 1200 x 900 pixels. Now, using the Paste From... command under the Edit menu, paste in one of the graphics, scroll it out of view, paste in another, and repeat the process for each graphic you want to display in one Help topic. As you paste your graphics into Paintbrush, pay close attention to how the colors appear. If one picture appears “compromised,” that is its colors seem corrupt, you may need to alter the order in which the graphic images are pasted into Paintbrush, starting with the one that has the most demanding palette.

    What we have done here is create a group of bitmaps that share a common palette that is optimized for all those graphics involved. Once you have these pasted into Paintbrush, use the rectangular Pick Tool to select your individual bitmaps. From the Edit menu, select Copy To... and save the new bitmap with its new, shared palette. Repeat this for each of the other graphic files you pasted into Paintbrush until all are saved with the optimized palette. (HINT: save the “collective” bitmap containing all the pasted graphics until after you have finished the complete conversion process. This way, if anything gets ‘screwed up’ later in the conversion process, you can start from this point again to regain what you might have lost, instead of repeating all the steps up to this point.)

    Third, now that you have all your bitmaps for one topic sharing a common palette, load each one into PixFolio and save each one as a WMF. I recommend PixFolio because it is the only shareware graphics conversion program I've found that properly supports writing files in the correct WMF format.

    Fourth, load each 256 color WMF into the Hot Spot Editor and save the file as a hot spot graphic file (SHG). While it doesn't matter if you define any hot spots in the 256 color WMF, this is a good opportunity to create a bona-fide 256 color hot spot graphic if you choose, complete with tons of hot spots. Saving the WMF as a SHG file reduces the overhead in your Help file, since the SHG file will be smaller than the comparable WMF file (same thing goes for BMP files...in fact, I recommend using nothing but SHG files, since their resultant size is smaller than the comparable graphic from which it was created).

    Insert your finished SHG file into your Help file using a referenced bitmap (that is: \{ bmc filename.shg \}). Compile your Help file as you normally would and you will see your 256 color graphic displayed in all its glory.

    Example:

    {bmc bm19.WMF}

    Thi    s
    256 color graphic
    is displayed using the techniques described above and is not under the control of an external DLL. Even though I use EW256BMP.DLLEW256BMP with this Help file, mostly to extract the necessary “extra” files from Help’s baggage file system, it has nothing to do with its display. If you don't believe this, try printing out this topic. If the picture above was under the control of my DLL, the picture will not print out. The picture, however, is printed when using the techniques discussed above.

    Now earlier, I mentioned Microsoft’s VidEdit program as one possible program you might want to have on hand. This program is available as part of the Video For Windows Development Kit, which appears on the Microsoft Jumpstart CD-ROM. I got my copy in Volume II of the 5 Ft. 10 Pack of CD-ROMS, which I purchased for around $30 when I bought my CD-ROM drive (quite a bargain, since you get 10 CD-ROMs for roughly $3 each). You can use VidEdit to optimize your palettes, if you like. Here’s how to do it.

    First, paste each graphic into VidEdit as if each were a frame in a digital video you were constructing. When all of the images have been pasted in, from the Video menu, select Create Palette. All the pasted images will be converted to a common palette.

    When it is finished, select each image and export it as a Windows Bitmapped Image file (BMP). Actually quite easy and a lot less trouble than the Paintbrush method listed eariler. I only listed the Paintbrush method earlier because this method is available to everyone while this method is available to only those who have access to the Video For Windows Development Kit.

    If you have access to the Video For Windows Development Kit, try this method. An added advantage is if you want all your images resized to the same dimensions, VidEdit will do that for you as well.

    One other potential “pothole” that’s easy to stumble into is to carefully pay attention to the screen resolution in which you are doing all your pre-Help file image manipulation in. Best results, that is images that display best at all resolutions and are properly scaled, are achieved if you do your image manipulation and conversions in a 640 x 480 x 256 color display resolution. Avoid, if at all possible, doing image manipulation and conversions at 800 x 600 x 256 with large fonts or 1024 x 768 x 256 with large fonts. These two resolutions offer less than optimal results if displayed on screens with a different resolution.

    Of course, your video driver plays a role in all of this, as well. Thus, it is important that you view your finished Help file in all possible resolutions prior to distribution to insure proper display of graphic files.

    Have fun displaying 256 color images in WinHelp 3.1!

    +$#KPlaying WAV Files I
    n WinHelp

    Although this is one     “
    custom macro” that many Help authors know about, I include it here for those just getting started with Help authoring. In fact, this seems to be one frequent and common question among “newbies” to the WinHelp newsgroup on the Internet.Internet

    Windows Help can also be your “stage to the world,” made capable of playing sound, music, and simple animations by registering the functions of the DLL mmsystem.dll, located in your Windows System directory. Virtually all users of Windows 3.1 or higher have this DLL so, once again, a whole other realm of possibilities for adding in increased functionality to your Help files opens up, without you having to distribute any additional files with your Help file.

    While this section is not intended to be a complete tutorial on how to use the multimedia control interface library with Windows Help (which would take more time and space, than I presently have), I do present it here to “clue you in” on some of the features you can include. I have provided an example of one of the most common multimedia commands used by Help authors at the end of this topic. The rest of it will have to be learned on your own. Perhaps one of the best places is to study the commands and their formats in the Help file Windows 3.1 Multimedia Reference (mcistrwh.hlp or mcistr.hlp).

    The most common multi    media command in mmsystem.dll used by Help authors, it is the sndPlaySound function. Below is how it is defined in the “mmsystem.h” C-language header file:

    /**********************    ******************************************************
    Sound support
    ****************************************************************************/

    BOOL WINAPI sndPlaySound(LPCSTR lpszSoundName, UINT uFlags);

    /* flag values for wFlags parameter */
    #define SND_SYNC 0x0000 /* play synchronously (default) */
    #define SND_ASYNC 0x0001 /* play asynchronously */
    #define SND_NODEFAULT 0x0002 /* don't use default sound */
    #define SND_MEMORY 0x0004 /* lpszSoundName points to a memory file */
    #define SND_LOOP 0x0008 /* loop the sound until next sndPlaySound */
    #define SND_NOSTOP 0x0010 /* don't stop any currently playing sound */

    #endif /* ifndef MMNOSOUND */

    And using this command     is quite easy. Below are the parameters and usage for Help file authors:

    Syntax:
    sndPlaySound(`sound name', flag-integer)

    Parameters:
    sound name Specifies the fully qualified path and filename of the .WAV file to play when the macro is executed. Or, if you so choose, you may specify one of the sound “events” listed in the [sounds] section of the WIN.INI file. Typically, these will be minimally defined as SystemQuestion, SystemHand, SystemExclamation, SystemAsterisk, or SystemDefault. There may be others defined, depending on how your system is configured. Sticking with these, however, will pretty much insure compatibility between most user’s systems. If the sound specified does not exist, or if the specified .wav file cannot be found, the default system “beep” is played.

    flag-integer Specifies how the sound file (specified in the filename parameter) is played. This value must be one of, or a combination of, the following hexidecimal values:

    Value Flag Description

    0    x0000 SND_SYNC Play sound synchronously (default). Does not “free up” the system until the sound is finished playing.
    0x0001 SND_ASYNC Play sound asynchronously. “Frees up” the system as soon as the sound starts to play.
    0x0002 SND_NODEFAULT Don’t use default sound in WIN.INI. If the sound file cannot be found, it remains silent, playing nothing at all.
    0x0008 SND_LOOP Loop (repeat) the sound until next sndPlaySound command. Repeats the sound over and over until the next sndPlaySound command is issued with empty quotes ( `', not ` '; the latter has a space between them) as the filename. Must also be used with the SND_ASYNC flag.
    0x0010 SND_NOSTOP Don’t stop any currently playing sound to play another.

    Notice: some sound
    drivers (i.e., PC Speaker Driver) will not allow you to combine some of the above listed flags, or even use others. For example, the PC Speaker Driver will not support the SND_LOOP flag. Nor can you combine flags with this particular driver. However, you should also be able to combine the flags for those using sound cards. You should simply be able to add them together (i.e., 0x0001 + 0x0002 + 0x0008 = 0x000B). Remember, you are adding hexidecimal numbers, where A = 10, B = 11, C = 12, D = 13, E = 14, and F = 15. (Suggestion: to eliminate errors in adding hexidecimal numbers, go to the scientific view of “Calculator” that comes with Windows. Select the HEX radio button, and you can add the hexidecimal values together).

    Returns:
    TRUE if     the sound is played, FALSE if it is not.

    RegisterRoutine Format:
    RR(`mmsystem.dll',`snd    PlaySound',`Su')

    Usage:
    sndPlaySound(`tada.wav',0x0000)
    sndPlaySound(`c:\\sounds\\r&b.wav',0x0001)

    Comments:
    As with all Help macros, the maximum length is limited to a total of 254 characters or less.

    Note that if you specify the flag as SND_ASYNC (0x0001) and you have “Enable Sound Interrupts” checked in the sound driver's setup, there may be a loss of “sound fidelity” if you move your mouse all over the screen or do other tasks, since Windows must constantly check for interrupt calls to update whatever else it is you are doing while the sound is playing. This is totally dependent on your sound driver. To see an example of this, click on the demonstration button at the end of this topic and move your mouse like mad all over the window, paying attention to the quality of the sound. Next, click on the demonstation button again, but this time, leave your mouse perfectly still. When I used the measly PC Speaker Driver before obtaining a sound card, I could tell a noticable difference.

    If you want to play a sound that is in another directory, you must use four (4) backslashes for each backslash you would normally include when providing a path and filename to specify the sound to play. See the example above, under the Usage section.

    Make sure the file extension, .WAV, is included or Windows will not be able to play your sound.

    Supplemental Sources:
    Try to get your hands on two of Martin Heller’s columns that appeared in the July and August 1992 issues of Windows Magazine. The July article talks about using the MCI (Multimedia Control Interface) commands and the August article talks about extending Windows Help with DLL’s. If you cannot locate these articles, I suggest downloading the file “heller92.zip” from the Windows Magazine forum on America Online. This is a collection of all his articles from 1992, in ASCII text format. Many of the articles include source code to demonstrate the particular topic. From Go To... in the AOL software, type the keyword WinMag.

    Example:

    {bmc bm18.BMP}! Extract
    Bag(qchPath, `iregular.wav', `c:\\iregular.wav', qError);sndPlaySound(`c:\\iregular.wav', 0x0001);DeleteFile(`c:\\iregular.wav')

    +$!#KOther Multimedia

    Devices in WinHelp

    A    s we said before, Wind
    ows Help can also be your “stage to the world,” made capable of playing sound, music, and simple animations by registering the functions of the DLL mmsystem.dll, located in your Windows System directory.

    While this section is not intended to be a complete tutorial on how to use the multimedia control interface library with Windows Help (which would take more time and space than I presently have), I do present it here to “clue you in” on some of the features you can include. I have provided an example of one of the most versitle multimedia commands used by Help authors at the end of this topic. The rest of it will have to be learned on your own. Perhaps one of the best places is to study the commands and their formats in the Help file Windows 3.1 Multimedia Reference (mcistrwh.hlp or mcistr.hlp).

    The most versitile mul    timedia command in mmsystem.dll used by Help authors, it is the mciSendString function. With it, you can play MIDI files from Windows Help or control the playback of an audio CD.

    You communicate with the MCI (Media Control Interface) with strings. Martin Heller, accomplished author of several Windows Programming books and penman of a monthly column in Windows Magazine, puts it in proper perspective when he says: “It's a puppet...It works with strings.”

    There are quite a few command “strings” for this command, but most of the time, you will only be concerned with seven of them. And they are extremely easy to use. Here is the format for using this “macro:”

    First, register the macro with Windows Help with the following command:

    RegisterRoutine(`mmsystem.dll',`mciSendString',`SSuu')

    Then, you can execute     the macro using the following format:

    mciSendString(`command string',`',0,0)^"text/bitmap"

    As with all Help macr    os, you can create a macro chain by chaining several executions of this macro by separating individual commands with a semicolon. And, you are limited to a total length of 254 characters in your macro/macro string.

    Furthermore, you may use this format with the Hot Spot Editor as well. Simply define your hot spot as a macro hot spot and type in the macro/macro string. Simply transplant the macro command into the macro definition box of the Hot Spot Editor. (This is how I made the “playback controls” at the bottom of the screen for the demonstration).

    Now, here are the seven MCI command strings you will probably use most often:

    open opens a file/device for playback.

    play plays the specified file/device.

    pause pauses playback of the specified file/device at its current position.

    stop stops playback of the specified file/device and resets the position to the beginning.

    alias assigns an alternate name to a file/device for use by subsequent command strings.

    wait waits until the file/device has finished before returning control to the application that called it.

    close closes the file/device.

    Scratching your head? Well, don’t. These commands are SO easy to use that once you try them, you'll see clearly how they work. Here are three simple examples:

    mciSendString(`open canyon.mid alias canyon',`',0,0);mciSendString(`play canyon wait',`',0,0);mciSendString(`close canyon',`',0,0)

    mciSendString(`open c    daudio',`',0,0);mciSendString(`play cdaudio wait',`',0,0);mciSendString(`close cdaudio',`',0,0)

    mciSendString(`open t    ada.wav alias tada',`',0,0);mciSendString(`play tada wait',`',0,0);mciSendString(`close tada',`',0,0)

    The first example open    s up the MIDI file “canyon.mid” and assigns the alias “canyon” to it. The alias provides an easier way to identify the file/device and must be used with all subsequent command strings once issued. Once it is opened, it plays the file “canyon.mid” (alias “canyon”) and does not return control to Windows Help until it is finished playing (You can terminate the “wait” state by pressing “CTRL + BREAK” and MCI will attempt to return control to the application that called it without interupting the command associated with the “wait” command). When it is finished playing, it closes the “canyon.mid” file. This assumes you have a MIDI interface (many sounds cards do) and that you are starting in the \WINDOWS directory.

    The second example opens the “cdaudio” device, plays the audio CD, and when it is finished, closes the device (the CD). This all assumes, however, that you have an audio CD in the CD-ROM drive, and that the drive door is closed.

    The third example opens the file “tada.wav” and assigns the alias “tada” to it. It then plays “tada” and closes the file when it is finished. This assumes you have a sound card and are starting in the directory, also.

    See...it's actually VERY easy. Here are some things you might want to be aware of.

    First, make sure Help can find the file you specify. You can remove all doubt by placing the sound file in the WINDOWS directory. Or, you can specify the full path and filename when you “open” the file (Note: remember, wherever you would use a \ in the path, you must type FOUR (4) backslashes in order for it to be parsed properly by the Help Compiler...like this: c:\\\\music\\\\mywave.wav). The other alternative is to specify the working directory for the Help file in the Properties dialog box in program manager and keep the file in the same directory as the Help file. Note that this WILL NOT WORK unless you have the working directory set!

    Second, keep in mind that you cannot use this macro to play back .WAV files on synchronous sound drivers, like the PC Speaker Driver. The MCIWAVE device included with Windows requires an asynchronous waveform driver.

    Now, check out the examples below. A brief description of each will appear following each one. Note that the first one requires you have a sound card/MIDI Interface to play. The second one requires that you have a CD-ROM drive and an audio CD in the drive.

    {bmc bm1.SHG}

    Click on
    the green arrow
    to play the MIDI file. Click on the blue bars to pause the MIDI file. Click on the red square to stop the MIDI file song and close it.

    By the way, here are the definitions for the macros associated with each button to play the MIDI file, as I defined them in the Hot Spot Editor:

    PLAY: mciSendString(`open c:\\dos\\game.mid alias game',`',0,0);mciSendString(`play game',`',0,0)

    PAUSE: IfThenElse(IsM    ark(`pause'), `DeleteMark(`pause');mciSendString(`play game', `', 0, 0)', `SaveMark(`pause');mciSendString(`pause game',`',0,0)')

    STOP: mciSendString(`    stop game',`',0,0);mciSendString(`close game',`'0,0)

    {bmc bm2.SHG}

    The “    Hot
    Spot CD-ROM Pla
    yer” is a bit more complicated...but obviously possible. It simply requires a bit of planning on your part to get everything right. It actually took me just a little more than two hours to get everything right...not a lot of time when you consider how much time you would have to spend if you were writting a custom DLL or a stand-alone program to do the same thing. And this short amount of time includes the time I spend drawing the graphic in Windows Paintbrush.

    The first consideration is to open the MCI device. Now, I could do that when the user selects the “Play” button, but then what if the user selects one of the track buttons towards the bottom of the graphic? The device is not yet opened and the selection will not play. So, to work around this limitation, execute a macro when you enter the topic that opens the CD-ROM device for CD Audio and set the time format (oh yes, this last consideration is a must in order for the individual track buttons to function properly). And, just to be sure that you don’t leave any MCI devices open when you exit the Help file, you could close the device when the user selects the Exit button on the button bar. Usually, though, any open MCI devices are closed when the application that called them (WinHelp) closes, so for most purposes, this is a moot point. No harm will be bestowed upon you if you don’t purposefully close the MCI device when you exit the Help file. The execution of the macro when you entered this topic is like this:

    mciSendString(`open cdaudio', `', 0, 0);mciSendString(`set cdaudio time format tmsf', `', 0, 0)

    Now that our “cdaudio”     device is open and the time format set to “tmsf” (tracks, minutes, seconds, frames), we can define the hot spots for the buttons on our hot spot graphic. Below is the definitions of those hot spots that execute the following macros:

    PLAY: mciSendSting(`play cdaudio', `', 0, 0)

    PAUSE: IfThenElse(IsM    ark(`paused'), `DeleteMark(`paused');mciSendString(`play cdaudio',`',0,0)', `SaveMark(`paused');mciSendString(`pause cdaudio',`',0,0)')

    STOP: mciSendString(`    stop cdaudio', `', 0, 0)

    EJECT: IfThenElse(IsM    ark(`open'), `DeleteMark(`open');mciSendString(`set cdaudio door closed',`',0,0)', `SaveMark(`open');mciSendString(`stop cdaudio',`',0,0);mciSendString(`set cdaudio door open',`',0,0)')

    HELP: HELP_CD (contex    t string for popup Help topic)

    TRACK BUTTONS: mciSen    dString(`seek cdaudio to x', `', 0, 0);mciSendString(`play cdaudio', `', 0, 0)

    There are a few things     that might need a little further explanation. First, when you select the Pause button, it checks for the presence of a text marker that signifies whether or not the CD is paused. If it exists, then that mark is deleted and the “play cdaudio” command string is executed to resume playing the CD. If it does not exist, then the mark is created and the “pause cdaudio” command string is executed to pause the CD. This way, the Pause button acts like a toggle switch, pausing the CD if it is playing and resuming play if the CD is paused.

    Second, the Eject button checks another text marker to see if the CD-ROM’s door is opened or closed. If the text marker (`open') exists, then the marker is deleted and the CD-ROM’s door is closed. If the text marker doesn’t exist, then the text marker is created and the CD-ROM’s door is opened. Again, this way the Eject button acts like a toggle switch, opening the CD-ROM’s door if it is closed and closing it if it is open.

    Third, the Track Buttons at the botton of the hot spot graphic correspond with the numbered tracks of the CD. I have allowed for access up to and including the first 20 tracks on a CD...ample, since most CDs don’t have that many tracks. Each of the buttons jump to the corresponding track of the CD and the ‘x’ in the macro definition above is replaced with the appropriate track number for each button. What if your CD doesn’t have 20 tracks, say only 12? Selecting any of the buttons above the number 12, then, does absolutely nothing. The track must exist for it to be played, otherwise the command is essentially ignored.

    With the Track Buttons described above, I could also alter their behavior further. As they exist now, it jumps to the corresponding track and begins playing from there. Thus, if you select the track button labelled “ 3 ” playback would start with track number 3, and proceed with playback of track numbers 4, 5, 6, 7, and so on. But, if you wanted it to playback only track number 3, you could alter the “play cdaudio” command string to read as follows:

    mciSendString(`play cdaudio from 3 to 4', `', 0, 0)

    and only track number     3 would be played. For the other tracks, you would simply substitute the appropriate numbers for ’ 3 to 4 ”. Make the number after “from” the track you want to play and the number after “to” the number of the track immediately following the one you want to play. Simple!

    See how much fun...and how easy...the MCI devices can be?!! But I'm not through yet...check out the Playing AVI Files In WinHelpVideo section.

    +$!#KPlay    ing AVI Files In
    WinHelp

    Just as we can use the     M
    CI commands to control the playback of waveform audio files, MIDI files, and audio CDs, we can also use the MCI command interface to control the playback of digital video. Note, however, that you must also have theMicrosoft Video For Windows Runtime Library v1.1dVFW in order to playback and interface with the MCI digital video commands. If you do not have this library, I strongly urge you to download and install it. Plus, any Help file you distribute should also be either accompanied by this runtime library or the users told where to get it. Thankfully, this runtime library is now in wide distribution, thanks to the explosion of CD-ROM based multimedia software. Thus, most of your users should already have it installed.

    What I’m not going to talk about here is how to create your own videos. That is another volumous Help file all in itself. But what we will discuss is how to get WinHelp to display any AVI file you specify. And, it’s not all that difficult as others might have you believe. All we need to do is communicate with MCIAVI.DRV and we do that through the mciSendString function that we discussed in Other Multimedia Devices In WinHelpMIDI_CD previously.

    There     are actually two ways to approach displaying AVI files in WinHelp. The first way simply plays the AVI file and prohibits any user input until it is through. This is by far the easiest method. The other way is to allow the user more control over the AVI file’s display, much like we did with the CD player earlier. This requires a lot more planning on your part and several more steps to achieve this user interaction.

    SIMPLE PLAYBACK

    The simple playback method can display and close an AVI device all with one macro hot spot and is far and away the easiest way to display AVI files in WinHelp. Below is the format for the macro chain:

    mciSendString(`open c:\\dos\\bldgdem4.avi alias bldg style popup', `', 0, 0);mciSendString(`put bldg window at 160 160 0 0', `', 0, 0);mciSendString(`play bldg from 0 wait', `', 0, 0);mciSendString(`close bldg', `', 0, 0)

    {bmc bm20.BMP}! mciSend    String(`open c:\\dos\\bldgdem4.avi alias bldg style popup', `', 0, 0);mciSendString(`put bldg window at 160 160 0 0', `', 0, 0);mciSendString(`play bldg from 0 wait', `', 0, 0);mciSendString(`close bldg', `', 0, 0) Click on the graphic at left to see the above macro string executed, which will play an AVI file of a building demolition.

    Here is what’ going on in our macro chain. First, we open up the AVI device with the file bldgdemo.avi. The .avi file extension signifies to the MCI interface that this is a digital video device. We assign it the alias “bldg” which serves sort of as a nickname for our file and AVI instance. We also specify to display the AVI file in a popup style window. If you leave the “style popup” out, then the AVI file will be displayed in a captioned window. It’s a personal choice as to how you choose to display the AVI window, although I prefer the popup style (I think it looks “cleaner”).

    Next, we define a location on the screen to display the AVI window. I like 160 and 160 because regardless of the display resolution, the video is always going to be visible and it’s a convenient, easy number to remember. The 160 160 entry places the AVI window 160 screen pixels down and from the left on the screen. The last two digits, 0 0 (zero and zero) tells MCIAVI.DRV to let the video display in its default size. This is by far the best choice. If you try to alter the size of a 24-bit video, the colors tend to get corrupted during playback, usually taking on a “greenish” cast.

    Third, we tell the MCI command interface to play the AVI with the alias “bldg” from the start (0 or zero) and to ignore any user input (wait) while it is playing. The “wait” command turns all control over to the AVI window until the AVI has finished playing. At that point, control is returned to the application that called it.

    Finally, we close the AVI device called “bldg” which removes the AVI window from the user’s screen.

    I know this seems complicated, but it’s actually more complicated to explain than to implement. Read on for how to allow user control over the playback process.

    AVIs WITH USER CONTROL

    The other way to display AVI files in WinHelp is a little more complex, since it allows more user control over the display of the file. Such user control is similar to the control we allowed from the user with the audio CD playback, where the user could stop, pause, play, or jump to tracks on the CD.

    Here, we will allow the user to play, stop, pause, rewind to the beginning, fast forward to the end, and seek forward and backward by 5 frame increments. First, below is a demonstration of the hot spot graphic that the user can interact with. Following that is an explanation of the macro chains for each of the buttons on the hot spot graphic.

    {bmc bm3.SHG}
    Select the
    STOP button to close the window
    when the AVI file is fi    nished playing.

    Here are the definition    s
    for the macros/macro chains used to define each of the buttons in the hot spot graphic and an accompanying explanation of each.

    PLAY: mciSendString(`open c:\\dos\\bldgdem4.avi alias bldg style popup', `', 0, 0);mciSendString(`set time format frames', `', 0, 0);mciSendString(`put bldg window at 160 160 0 0', `', 0, 0);mciSendString(`play bldg', `', 0, 0)

    Opens the specified AVI     file (bldgdem4.avi), assigns it the alias “bldg,” then displays the AVI file in a popup style window. Next, it sets the time format to frames, sets the location of the AVI playback window (160 160 0 0), and plays the AVI file with the alias of “bldg.”

    STOP: mciSendString(`stop bldg', `', 0, 0);mciSendString(`close bldg', `', 0, 0)

    Stops, then closes, the     AVI device/file with the alias of “bldg.”

    PAUSE: IfThenElse(IsMark(`pause2'), `DeleteMark(`pause2');mciSendString(`play bldg', `', 0, 0)', `SaveMark(`pause2');mciSendString(`pause bldg', `', 0, 0)')

    Checks to see if the te    xt marker “pause2“ exists. If the marker exists, it is deleted and playback of the AVI device/file with the alias “bldg” is resumed. If the marker does not exist, then the marker is created and playback of the AVI device/file with the alias of “bldg” is paused. This way, the Pause button acts like a toggle switch, pausing the AVI playback if the marker exists, and resuming playback if the marker doesn’t exist.

    REW TO BEGINNING: mciSendString(`seek bldg to start', `', 0, 0)

    Sets the playback posit    ion of the AVI device/file with the alias of “bldg” to the start of the AVI file. Kind of like an “instant rewind.”

    FAST FWD TO END: mciSendString(`seek bldg to end', `', 0, 0)

    Sets the playback posit    ion of the AVI device/file with the alias of “bldg” to the end of the AVI file. Kind of like an “instant” fast forward.

    REVERSE: mciSendString(`step bldg by -15 wait', `', 0, 0);mciSendString(`play bldg', `', 0, 0)

    “Steps” the playback po    sition of the AVI device/file with the alias of “bldg” backwards by 15 frames, pauses until the new location can be located (wait), and resumes playing the AVI device/file with the alias of “bldg” from the new position. The value you use to specify how many frames to go back depends on the frame rate of the AVI file you are playing. In this case, each click of the mouse on this button reverses the playback position by one second.

    FAST FWD: mciSendString(`step bldg by 15 wait', `', 0, 0);mciSendString(`play bldg', `', 0, 0)

    “Steps” the playback po    sition of the AVI device/file with the alias of “bldg” forwards by 15 frames, pauses until the new location can be located (wait), and resumes playing the AVI device/file with the alias of “bldg” from the new position. The value you use to specify how many frames to go forward depends on the frame rate of the AVI file you are playing. In this case, each click of the mouse on this button advances the playback position by one second.

    There...now that’s how you can provide for user interaction in the playback of AVI files in WinHelp. I did tell you that this method required a lot of planning and was considerably more complicated.

    GENERAL ISSUES CONCERNING AVI PLAYBACK

    There are several thing    s you need to be aware of when playing back AVI files from WinHelp. First, like I stated at the beginning of this topic, the Video For Windows Runtime Library v1.1dVFW is required. If your user does not have this runtime library, they should either be directed where to get it from or you might want to include it in your distribution disks.

    Second, many “stock” AVI files you find on CD-ROMs, major online services, and BBSs are 24-bit color AVIs. These files are rather large. To save space, you might want to convert the AVI file from 24-bit color to 8-bit color (236 colors). You can use Microsoft’s VidEdit (available in the Video For Windows Development Kit and on the Microsoft Multimedia Jumpstart CD-ROM) to alter the color depth. Your space savings are usually remarkable enough to seriously consider.

    Third, many of the aforementioned “stock” AVI files are recorded using the Indeo R3.2 compression routine. While this compression scheme works well for those folks with Indeo-capable graphics cards, displaying it on a graphics card without Indeo video capabilities produces jerky, less-than-optimal results. Plus, from what I've found, files using the Indeo compression scheme tend to be a greater disk hog than some of the files created using the other compression schemes available. Instead, I change all the AVI files I use to the Microsoft Video 1 compression scheme. It tends to be much more compatible with a greater variety of graphics cards and achieves better compression, resulting in smaller files. Also, Microsoft RLE compression yields very good results, but the RLE driver is not in as wide distribution at this time.

    Fourth, one problem arises with the “interactive” method of displaying AVI files in WinHelp. It is dependent on the end user selecting the Stop button to remove the AVI playback window from the screen. If a user forgets or fails to select the Stop button before exiting the Help file, the AVI playback window will be left displayed on the screen. If you’ve used the popup style playback window, there is no way of removing it from the screen without opening the Help file back up and properly closing it before re-exiting the Help file.

    My personal conclusion is that I prefer more to use the “simple” playback method mentioned earlier in this topic than the latter “interactive” method. I like to have as few variables and uncertainties as possible regarding whether or not the end user is going to do what needs to be done.

    $#Hot Spot CD-Player Help

    {    bml bm21.BMP}Plays the au
    dio CD in the CD-ROM drive from the beginning.

    {bml bm23.BMP}Stops the playback of the audio CD in the CD-ROM drive.

    {bml bm22.BMP}Pauses the playback of the current audio CD in the CD-ROM drive.

    {bml bm24.BMP}If the audio CD is currently playing, the playback is halted and the CD-ROM drive’s door is opened. If the door, however, is already opened, it is closed.

    {bml bm25.BMP}Displays this Help topic.

    {bml bm26.BMP}Begins playback of the audio CD from the indicated track and procedes to play any and all tracks occurring after that track.
    $#The Microsoft Video For Windows Runtime Library v1.1d is available from most BBSs, major online services (America Online, Prodigy, CompuServe, etc.), the Microsoft FTP site (ftp.microsoft.com), with many multimedia packages (Microsoft Cinemania, Microsoft BookShelf, Microsoft Encarta), the Video For Windows Development Kit, the Microsoft Multimedia Jumpstart CD-ROM, and many other sources. This runtime library is in free distribution and may also be distributed royalty free with your Help files that use Microsoft Video For Windows AVI files.
    $#From one of the USENETInternet ftp sites,
    download the following files:

    whndb2a.zip
    whndb2b.zip
    whndb2c.zip
    +$#KCreating Non-sizable WinH
    elp Windows

    There are going to be times
    (if it hasn’t already happened) that you will want to prevent the user from changing the size — thus the appearance — of the Help window. Say you have meticulously planned and laid out a really attractive screen display but the possibility of the end user changing the size of the Help window will cause your carefully planned layout to not display as you intended.

    To do this, we need to disable the Size menu item on the window’s system menu. This is actually quite easy to do, using routines found in USER.EXE. Place the following lines in the [Config] section of your HPJ file:

    RegisterRoutine(`user',`SetWindowLong',`UiU')
    RegisterRoutine(`user',`Set    WindowPos',`Uuiiiiu')
    SetWindowLong(hwndApp, -16,     0x16C80000)
    SetWindowPos(hwndApp, 0, 0,     0, 0, 0, 0x0027)

    The window cannot be sized     either by the Size menu item of the window’s system menu, nor by dragging the window’s border.

    However, in playing around with this one, I’ve discovered a few more combinations you might want to employ. Below is a list of them, with a brief explanation of each:

    To remove the system menu and the maximize button, and leave the minimize button. Allows resizing of the window via the borders:

    Place the following in the [Config] section of the HPJ file:

    RegisterRoutine(`user',`SetWindowLong',`UiU')
    RegisterRoutine(`user',`Set    WindowPos',`Uuiiiiu')
    RegisterRoutine(`user', `Sh    owWindow', `Ui')
    SetWindowLong(hwndApp, -16,     0x16C60000)
    SetWindowPos(hwndApp, 0, 0,     0, 0, 0, 0x0027)

    Then, execute the following     macro as you enter the CONTENTS topic of your Help file:

    ShowWindow(hwndApp, 0);ShowWindow(hwndApp, 1)

    The ShowWindow custom macro     hides and then redisplays the main Help window, allowing the changes to be redrawn.

    To remove the system menu, minimize and maximize buttons. Allows resizing of the window via the borders:

    Place the following lines in the [CONFIG] section of your HPJ file:

    RegisterRoutine("user","SetWindowLong","UiU")
    RegisterRoutine("user","Set    WindowPos","Uuiiiiu")
    RegisterRoutine(`user', `Sh    owWindow', `Ui')
    SetWindowLong(hwndApp, -16,     0x16C40000)
    SetWindowPos(hwndApp, 0, 0,     0, 0, 0, 0x0027)

    Then, execute the following     macro as you enter the CONTENTS topic of your Help file:

    ShowWindow(hwndApp, 0);ShowWindow(hwndApp, 1)

    The ShowWindow custom macro     hides and then redisplays the main Help window, allowing the changes to be redrawn.

    To remove the system menu, maximize button, and the thick window border (provides no chance to resize the window), but leaves the minimize button:

    Place the following lines in the [CONFIG] section of your HPJ file:

    RegisterRoutine("user","SetWindowLong","UiU")
    RegisterRoutine("user","Set    WindowPos","Uuiiiiu")
    SetWindowLong(hwndApp, -16,     0x16C20000)
    SetWindowPos(hwndApp, 0, 0,     0, 0, 0, 0x0027)
    PositionWindow(0,0,0,0,2,`m    ain')

    The execution above of the     standard WinHelp macro Position Window (may also be abbreviated PW) causes the main WinHelp window to be started initially in a minimized state.

    Then, execute the following PositionWindow macro when you enter the CONTENTS topic of your Help file:

    PositionWindow(0,0,682,1023,1,`main')

    The above standard WinHelp     macro PositionWindow redisplays the main Help window, allowing the changes to the window’s style to be redrawn. Be sure to pass along the same window positioning and sizing information you originally specified in the window definition statement in the [WINDOWS] section of your HPJ file. Use of this macro is required, since the ShowWindow custom macro used earlier doesn't work quite right with the altered window frame style.

    To remove the system menu, minimize and maximize buttons, and the thick window border:

    Place the following lines in the [CONFIG] section of your HPJ file:

    RegisterRoutine("user","SetWindowLong","UiU")
    RegisterRoutine("user","Set    WindowPos","Uuiiiiu")
    SetWindowLong(hwndApp, -16,     0x16C00000)
    SetWindowPos(hwndApp, 0, 0,     0, 0, 0, 0x0027)
    PositionWindow(0,0,0,0,2,`m    ain')

    The execution above of the     standard WinHelp macro Position Window (may also be abbreviated PW) causes the main WinHelp window to be started initially in a minimized state.

    Then, execute the following PositionWindow macro when you enter the CONTENTS topic of your Help file:

    PositionWindow(0,0,682,1023,1,`main')

    The above standard WinHelp     macro PositionWindow redisplays the main Help window, allowing the changes to the window’s style to be redrawn. Be sure to pass along the same window positioning and sizing information you originally specified in the window definition statement in the [WINDOWS] section of your HPJ file. Use of this macro is required, since the ShowWindow custom macro used earlier doesn't work quite right with the altered window frame style.

    Personally, I like the fourth implementation best. It removes the system menu, the maximize button, and the thick window frame, leaving only the minimize button on the title bar.

    Just be cautious with the implementations of this macro that cause the system menu to be removed from the window. Without the system menu, the only way a user has to close the window is with a button on the button bar (or a macro hot spot on the screen) that executes the Exit() macro when selected.

    This is kind of fun to play around with, since it gives a different appearance to the customary Help window by removing items that, before now, have always been there and by altering the style of the Help window (e.g., changing to a window style that has no thick border).

    Also, I have not provided an example of this custom macro here, preferring instead to maintain the normal Help file appearance.

    +$#KJumping To Topics In Secon
    dary Help Windows From the Search Dialog Box

    One “tip” or special macro i    m
    plementation that has been found lurking on the InternetInternet briefly from time to time is how to jump to a topic from the Search dialog box and have that topic appear in a secondary window. Although rather complex, it works charmingly and could benefit many Help authors who want to display a particular topic in secondary window while leaving other important information in the main Help window.

    Without keeping you in suspense, here is the macro that accomplishes this:

    IfThenElse(Not(IsMark(`StopRecurse')), `SaveMark(`StopRecurse');Back();JI(`myhelp.hlp>secwind', `NAME_OF_TOPIC')', `DeleteMark(`StopRecurse')');FocusWindow(`secwind')

    Of course, this should all b    e typed in on one line. Here is what’s going on. First, using the conditional WinHelp macro IfThenElse we check to see if a text marker “StopRecurse” exists. If it does not, then the text marker is created, the Back() macro is executed to display the previously displayed topic and displays the specified topic in the Help file “myhelp.hlp” in the secondary window called “secwind.” If the text marker exists (as it will when the topic displays itself in the secondary window), the text marker is deleted and the focus is shifted to the secondary window.

    Here are some considerations you need to keep in mind. First, you execute this macro upon entering a topic (a topic macro). Second, the Help file specified in the JumpID macro should be the same filename as the current Help file.Third, “NAME_OF_TOPIC” should be the exact context string text for the topic that contains this macro. The topic is, essentially, calling itself.

    The reason for the Back() macro execution is to prevent displaying the same topic in both the main and secondary window. The Back() macro causes the previous topic to reappear in the main window, while the current topic is displayed in the secondary window.

    If you don’t immediately understand it, don’t worry...it’ll soon start making sense. The first time I looked at this I was really scratching my head trying to figure out how it worked. But trust me, this does work.

    +$#KUsing The “C” Language #inc
    lude Directive

    Many Help authors already kno    w
    that the C-Language directive #include and #define can be used in the [MAP] section of the HPJ file to define context sensitive topic context numbers. If you are not familiar with this feature, refer to the Microsoft Help Writer’s Authoring Guide (HAG) available on one of the InternetInternet ftp sites, many major online services, and many more BBSs world-wide.

    But, what many Help authors don’t realize is that you can also use the #include statement in the [CONFIG] section of the HPJ file as well.

    Say, for example, in your Help authoring environment, you commonly execute the same macros at the start of virtually every Help file you construct. You could type them in each time for each HPJ file, but this would obviously waste a lot of time. After all, isn’t a computer supposed to make things easier by automating repetitive tasks?

    So, instead of simply typing them ALL in each time you create your HPJ file, simply place the following line in the [CONFIG] section of your HPJ file:

    #include <myheader.inc>

    This tells the Help Compiler to look for and read in the contents of the file named “myheader.inc” when it processes the [CONFIG] section of the HPJ file. This file, which should be located in the same directory as the HPJ file for your Help project, simply is a list of all the macros you like to execute by default when you create a Help file. Now, your many lines of macro declarations are replaced with the one line listed above. I have chosen, for my use, the file extension of .inc to signify that this is an include file for my Help projects. You can, however, use any file extension you want.

    Here is an example of what the contents of your #include file might look like:

    CreateButton(`btn_print', `&Print', `Print()')
    CreateButton(`btn_exit', `E&x    it', `Exit()')
    RR(`mmsystem.dll', `sndPlaySo    und', `Su')
    RR(`mmsystem.dll', `mciSendSt    ring', `SSuu')
    RR(`user.exe',`MessageBox',`U    SSu')
    RR(`user',`SetWindowLong',`Ui    U')
    RR(`user',`SetWindowPos',`Uui    iiiu')
    SetWindowLong(hwndApp, -16, 0    x16C20000)
    SetWindowPos(hwndApp, 0, 0, 0    , 0, 0, 0x0027)
    PositionWindow(0,0,0,0,2,`mai    n')

    ...and so on. This way, you d    on't have to keep worrying about declaring your commonly used macros over and over again and doing it correctly. You only have to do it once (correctly, of course), and insert that one, short, easy-to-remember line in the [CONFIG] section of your HPJ file.

    BY THE WAY... I could have sworn that I spied a brief mention of also using the C-language directive #define in the [CONFIG] section of the HPJ file to define and retain values for variables, but for the life of me, I cannot locate that information in my reams and reams of Help documentation. If anyone can shed some light on this (or let me know for a fact that I was hallucinating), please let me know via any of the contact methods listed in the WinHelp Report FormReport in this Help file. Hopefully, I can someday soon find this brief mention once again and be able to include it in WinAPI 4 when it comes out (if it pans out to be anything at all).

    +$#KDeleting/Modifying WinHelp’s
    Default Menus

    A great deal many Help authors
    have often lamented over the lack of control over WinHelp’s default menus, most of which can neither be deleted nor whose behavior can be altered. For instance, many Help authors would like to be able to disable the Copy menu item under the Edit menu, or the Print menu item under the File menu.

    There are actually two approaches to this...one involves deleting WinHelp’s menu bar altogether, while the other one disables individual menu items. Disabling the menu items is the most dependable and preferred method to altering the menus, however, since it retains the “familiar” interface Help users have become accustomed to. Plus, deleting the menu bar altoghether has a peculiar ‘quirk’ that I’ll talk about in a few minutes; if you can put up with it (and expect your end user to put up with it, as well), then perhaps you can go ahead and try it.

    DISABLING THE STANDARD MENU ITE    MS

    Like I mentioned earlier, this     i    s the preferred method of altering the behavior/state of the standard WinHelp menu items. This way, the familiar interface Help users have become accustomed to isn’t altered.

    This custom macro tip comes from Pete Davis, WinHelp Guru extraordinaire, who has assisted with unlocking the WinHelp file format, as well as that of the hot spot graphic SHG file format and several others.

    In the [CONFIG] section of your HPJ file, implement the following macro definitions:

    RegisterRoutine(`user.exe', `EnableMenuItem', `uuu')
    RegisterRoutine(`user.exe', `G    etSubMenu', `u=uu')
    RegisterRoutine(`user.exe', `G    etMenu', `u=u')
    RegisterRoutine(`user.exe', `G    etActiveWindow', `u=')
    EnableMenuItem(GetSubMenu(GetM    enu(GetActiveWindow()), 0), 2, 1027)

    In the above example, the Prin    t Topic menu item will be greyed out and disabled. The zero (0) specifies which top level menu to ‘work’ on...in this case the File menu. The menus are numbered from left to right, starting with zero. Thus, the Edit menu is number 1, the Bookmark menu is number 2, and the Help menu is number 3, provided you haven’t inserted any top level menus of your own.

    The two (2) in the EnableMenuItem macro above specifies which menu item to disable. The list of menu items, too, is zero-based (that is, start numbering the first menu item as zero, the second as 1, the third as 2, and so on). Since there is a menu seperator after the Open... menu item (which is number zero), the Print Topic menu item is number two (2), since the seperator counts as a menu item and is counted as menu item number one (1).

    The 1027 in the EnableMenuItem custom macro above is the attribute you want to set the menu item as (greyed and disabled).

    Another reason this is the much more preferred method is that it is much, much more reliable and doesn't produce the quirk I alluded to earlier with destroying the menu.

    For your convenience, here is a table of values for each standard WinHelp menu item:

    MENU ITEM Top-Level Menu Number
    Standard Menu Item Number

    F    ile|Open 0 (zero) 0 (zero)
    File|SEPERATOR 0 (zero) 1
    File|Print Topic 0 (zero) 2
    File|Print Setup 0 (zero) 3
    File|SEPERATOR 0 (zero) 4
    File|Exit 0 5
    Edit|    Copy 1 0 (zero)
    Edit|    SEPERATOR 1 1
    Edit|Annotate 1 2
    Bookmark    |Define 2 0 (zero)
    Help|How To Use Help 3 0 (zero)
    Help|Always On Top 3     1
    Help|SEPERATOR 3 2
    Help|Ab    out 3 3

    Now consi    der that these values
    above are valid only if you haven’t modified WinHelp’s standard menu items by adding in additional items. To re-enable any menu item disabled in this manner, reissue the EnableMenuItem custom macro, changing the 1027 to 1024.

    REMOVING WINHELP’S STANDARD MEN    US/MENU ITEMS

    Actually, there are two method    s     of removing WinHelp’s standard menu/menu items. It’s the second method I’ll list below that has the little ‘quirk’ to which I referred to earlier in this topic. The first method, which I’ll detail first, is very similar to the custom macro detailed above which disables and grays out the standard menu items. It is much more reliable and has no little ‘quirks’ that I can find.

    In the [CONFIG] section of your HPJ file, implement the following macro definitions:

    RegisterRoutine(`user.exe', `RemoveMenu', `uuu')
    RegisterRoutine(`user.exe', `G    etSubMenu', `u=uu')
    RegisterRoutine(`user.exe', `G    etMenu', `u=u')
    RegisterRoutine(`user.exe', `G    etActiveWindow', `u=')
    RemoveMenu(GetSubMenu(GetMenu(    GetActiveWindow()), 0), 2, 1024)
    RemoveMenu(GetSubMenu(GetMenu(    GetActiveWindow()), 0), 2, 1024)
    RemoveMenu(GetSubMenu(GetMenu(    GetActiveWindow()), 0), 2, 1024)

    Note that the only thing that     has changed from the previous section is the first custom macro RegisterRoutine statment (to register the RemoveMenu function) and the last line, which implements this new macro. Also, the last number specified in the custom macro implementation is 1024, instead of 1027 (although using 1027 has no ill effects). All the details for the EnableMenuItem custom macro above apply to this custom macro as well (including the table listing menus/menu items).

    Also, note that I have implemented the RemoveMenu custom macro three (3) times, each one removing menu item number two (2), which represents the third item in the menu. Doing this removes the Print Topic, Printer Setup, andone of the menu seperators. Each time I remove a menu item from the menu, everything else is “moved up” in the menu’s numbering heirarchy, so I simply keep deleting the third item a sufficient number of times to remove all the items I am interested in removing.

    The other method, involves completely removing the menu bar from WinHelp’s main window. But here is the little ‘quirk’ I referred to earlier: for some reason I cannot pinpoint (am I missing something very simple and basic here, right under my nose?), executing this from the [CONFIG] section of the HPJ file causes WinHelp to display an error message (Unable to add menu item.). However, if I execute this macro from a macro hot spot, I don't get this message. Certainly, if this were done under the control of a custom DLL, the error message wouldn’t be displayed. If you can live with this little quirk, then proceed with the macro implementation below. But, if you are like me (labelled a perfectionist), and you hate little imperfections like this one, perhaps the previous method using RemoveMenu is the preferred choice.

    In the     [CONFIG] section of the HPJ file, issue the following commands:

    RR(`user.exe',`GetMenu',`u=u')
    RR(`user.exe',`GetActiveWindow',`u=')
    RR(`user.exe',`DestroyMenu',`u    =u')
    RR(`user.exe',`SetMenu',`uu')
    DestroyMenu(GetMenu(GetActiveWindow()))
    SetMenu(GetActiveWindow(), 0)

    By the way, this custom macro implementation comes from Rob van Geel in the InternetInternet newsgroup postings (R.A.M.vGeel@kub.nl).

    Also, if you plan on implementing this custom macro, proceed with caution as I haven’t thoroughly tested it, but I do recommend either executing it from the [CONFIG] section of the HPJ file or from a macro hot spot located in the first topic of your Help file! WinHelp may produce a GPF if you attempt to execute this custom macro from a macro hot spot located somewhere other than the first topic of your Help file.

    The effect is kind of nice, in that it removes the menu bar, leaving only the button bar behind. It could be used to exercise a lot of control over your end user’s actions...if you can live with the ‘quirk.’

    However, if you are successful in supressing or working around the ‘quirk,’ I would appreciate hearing from you. Send in your findings/solution on the WinHelp Report FormReport at the end of this file and I’ll update everyone else in the next issue of WinAPI (when it comes out).

    +$#KExtended Characters With WinH
    elp

    Many Help authors, whether usin    g
    Microsoft Word or a third party Help authoring utility (i.e., MiniHelp Plus v3.21MHP321 to create Windows Help files, have found inserting special characters (those characters whose ASCII value is higher than 127) a trying and often frustrating task. In fact, many Help authors have resorted to importing their special characters as bitmapped images, a process that works but also increases the size of your Help file. Also, problems arise with this method if you do not “hard-code” your Help windows screen colors, allowing them to default to the user’s color scheme. A typical bullet as a bitmap will have a square region of white around the colored bullet and this white area will look really funky in a Help file that displays in the user’s default colors if the Application Workspace color is set to something other than white.

    The problem lies in the fact that the Help Compiler only recognizes ASCII characters 0 to 127. Attempting to directly type in characters with ASCII values higher than 127 will generate the notorious “Error at byte offset 0xXXXX” Help Compiler error statement and the Help file will not be compiled.

    Fortunately, there is an overloaded RTF command which enables the Help Compiler to display extended special characters and it will work with MS-Word or any Help authoring utility that allows you to embed RTF commands directly into the Help file’s source text.

    For Help authoring utilities that allow you to embed RTF commands directly into the Help file’s source text, simply select the character from the list below and type in the keycode. However, if you use MS-Word, read on for the solution.

    SPECIAL CHARACTERS WITH MS-WORD

    There are three methods to get sp    ecial characters to appear in Help files created from MS-Word. Since I can’t afford and I don’t have MS-Word, all this info is second hand but from what I’ve been able to gather from all the InternetInternet newsgroup postings, it is correct and should work for you. The following was posted there by Derek MacEwen of Canada:

    METHOD #1: Use Insert Symbol, but choose the “Normal Text” rather than the “Symbols” pulldown. Find the symbol you want to use and insert it. The version of the symbol may look a little different than the one chosen the other way, but it appears as if Word’s RTF convertor supports the normal text symbol set only.

    METHOD #2: With NumLock on, hold down the ALT key and type the 4-digit character code on the number pad for the character you want. The codes are listed in Appendix A of the Word 6.0 manual, or you can find them using Character Map that came with Windows. In Character Map, simply click the mouse once on the character you want to use and note the 4-digit code in the lower right hand corner of the window.

    METHOD #3: Edit the RTF file directly as an ASCII text file. Simply open up Notepad, DOS EDIT, or any other ASCII text file editor and view the RTF file. Insert the character code for the special character you want from the list below where ever you want that special character to appear. Note, however, that the RTF output from MS-Word is rather hard to read. But once you find out how Word is inserting a character, using a simple search-and-replace function should make it rather easy to replace the offending characters.

    ATTENTION MS-WORD 6.0 USERS!

    M
    ake sure you turn off smart quot
    es when typing in the text for your Help file, or you will get the dreaded, notorious Help Compiler error statement “Error at byte offset 0xXXXX” and your Help file will not be compiled. If you are unsure how to do this, refer to your manual (what is it they say...when all else fails, read the manual?) or the Word Help file (search for Smart Quotes).

    Now, below is a list of all the extended special characters with ASCII values from 128 to 255. International users take note, as many of the special language characters you require fall within this group.

    Extended Character Chart

    ‚ = \'8
    2 ƒ = \'83 „ = \'84
    … = \'85 † = \'86 ‡ = \'87
    ˆ = \'88 ‰ = \'89 Š = \'8A
    ‹ = \'8B Œ = \'8C  = \'8D
    Ž = \'8E  = \'8F  = \'90
    ‘ = \'91 ’ = \'92 “ = \'93
    ” = \'94 • = \'95 – = \'96
    — = \'97 ˜ = \'98 ™ = \'99
    š = \'9A › = \'9B œ = \'9C
     = \'9D ž = \'9E Ÿ = \'9F
    = \'A0 ¡ = \'A1 ¢ = \'A2
    £ = \'A3 ¤ = \'A4 ¥ = \'A5
    ¦ = \'A6 § = \'A7 ¨ = \'A8
    © = \'A9 ª = \'AA « = \'AB
    ¬ = \'AC = \'AD ® = \'AE
    ¯ = \'AF ° = \'B0 ± = \'B1
    ² = \'B2 ³ = \'B3 ´ = \'B4
    µ = \'B5 ¶ = \'B6 · = \'B7
    ¸ = \'B8 ¹ = \'B9 º = \'BA
    » = \'BB ¼ = \'BC ½ = \'BD
    ¾ = \'BE ¿ = \'BF
    À = \'C0 Á = \'C1 Â = \'C2
    Ã = \'C3 Ä = \'C4 Å = \'C5
    Æ = \'C6 Ç = \'C7 È = \'C8
    É = \'C9 Ê = \'CA Ë = \'CB
    Ì = \'CC Í = \'CD Î = \'CE
    Ï = \'CF Ð = \'D0 Ñ = \'D1
    Ò = \'D2 Ó = \'D3 Ô = \'D4
    Õ = \'D5 Ö = \'D6 × = \'D7
    Ø = \'D8 Ù = \'D9 Ú = \'DA
    Û = \'DB Ü = \'DC Ý = \'DD
    Þ = \'DE ß = \'DF à = \'E0
    á = \'E1 â = \'E2 ã = \'E3
    ä = \'E4 å = \'E5 æ = \'E6
    ç = \'E7 è = \'E8 é = \'E9
    ê = \'EA ë = \'EB ì = \'EC
    í = \'ED î = \'EE ï = \'EF
    ð = \'F0 ñ = \'F1 ò = \'F2
    ó = \'F3 ô = \'F4 õ = \'F5
    ö = \'F6 ÷ = \'F7 ø = \'F8
    ù = \'F9 ú = \'FA û = \'FB
    ü = \'FC ý = \'FD þ = \'FE
    ÿ = \'FF

    +$#KInternet Sources For WinHelp I
    nfo

    Just as with virtually everythin    g
    else, the interest in Windows Help files has led to several discussion groups and file transfer protocol sites for WinHelp information on the Internet. These resources are a great aid to WinHelp authors all over, since commonly needed files can easily be located and obtained, and since individuals (like myself) can discuss problems and issues concerning WinHelp construction with other similarly interested individuals world wide.

    Once you have hooked up to an Internet provider (and if you already subscribe to America Online or Prodigy, you already have this access...sorry, but CompuServe, the so-called “leader” of online services, is just now experimentally trying out Internet access as of the time of this writing), you can access any of the featured services listed below with ease.

    MAIL LISTS

    If you are having pr    oblems and need some assistance with a WinHelp problem, the mail list might be the place to ask them. Other interested individuals like yourself subscribe to this mail list and a strong possibility exists that some other WinHelp author has already encountered and solved a similar problem. This is not an extremely fast way to get answers, but efficient. Post your question in an E-Mail message to the mail list and within a few days, you are likely to have an answer (most likely, many answers).

    To subscribe to the mail list send E-Mail to:

    Listserv@Admin.HUmberC.ON.CA

    The subject field of your E-Mail is ignored, so place anything there. In the body of your E-Mail message, type the following:

    SUBSCRIBE WINHLP-L <Your Real Name>

    placing your own real name in pl    ace of <Your Real Name>, without the brackets.

    Within a manner of minutes, you will get a confirmation of your mail list subscription. Daily, the mail is posted to all subscribers E-Mail addresses, free of charge.

    I strongly suggest that once your subscription is confirmed, send another E-Mail message to the address above with the body of your E-Mail message as:

    set winhlp-L digests

    This will pack all the day’s E-Mail messages into one large E-Mail message. Otherwards, each individual post to the mail list gets sent seperately and on heavy days, the number of messages in the que may quickly overrun your E-Mail mailbox.

    To post messages to this mail list, address your mail to:

    Winhlp-L@Admin.HumberC.ON.CA

    INTERNET NEWSGROUPS

    The Inter    ne    t Newsgroups can be just as productive and informative as the mail lists. In fact, messages posted there might get a faster response than the mail lists. Only thing is, you’ll have to go in and look for them, since the responses typically are posted on the message board rather than mailed to your E-Mail mailbox.

    The newsgroup that is of most interest to WinHelp authors is defined as:

    comp.os.ms-windows.programmer.winhelp

    Some very astute WinHelp talent     lurks on these message boards (e.g., Pete Davis, Ron Lowry, Stefan Olson) and an answer to a problem you might be having can very often be quickly found, often within one or two days.

    Another newsgroup you might want to keep your eyes on is alt.hypertext. Usually not too much talk revolves around WinHelp here, but there is an occassional posting. Most of the emphasis of this newsgroup revolves around hypertext in general and most specifically, HTML and creating Wide World Web (WWW) pages. I subscribe to them both.

    WORLD WIDE WEB (WWW) SITE

    There     is a brand new World Wide Web (WWW) home page for WinHelp enthusiasts. Since America Online (my Internet provider) isn’t expected to have WWW capabilities until August, 1995 at the earliest, all I can provide you is the WWW address:

    http://www.interlog.com/~gbyrnes/winhelp.html

    FTP SITES

    There are several F    TP     (file transfer protocol) sites that may be of keen interest to WinHelp enthusiasts. Although I’m sure there are other sites of interest to WinHelp enthusiasts, those listed below are the major ones. If you find one that is relatively beneficial to you, send mention of it via the WinHelp Report FormReport and I’ll share it with everyone else in WinAPI 4, when it comes out.

    ftp.microsoft.com
    ..... Microsof
    t

    ftp.cica.indian    a.edu
    ..... Indiana University

    198.91    .36.126/pub/winhelp
    ..... GMUTANT site (usenet)(downloads)

    192.75.71.35/winhlp
    ..... HUMBER site (usenet)

    198.    91.36.126/pub/incoming
    ..... GMUTANT site (usenet)(uploads)

    ftp.mv.com
    ..... Dr. Dobb’s Journal source code files

    ftp.uu.com
    ..... Windows/DOS Programmer’s Journal files

    Like I mentioned earlier, these ar
    e some of the major players as far as WinHelp related files are concerned. Below is a list of files you might be interested in obtaining from GMUTANT usenet site:

    WHFAQ.HLP
    Windows Help Frequentl
    y Asked Questions file...WinHelp format.

    WINHELP.FAQ
    Windows Help Frequently Asked Questions file...ASCII text format

    WHNDB2A.ZIP, WHNDB2B.ZIP, and WHNDB2C.ZIP
    Help file of news    group postings related to Windows Help

    WHAT6.ZIP
    MS-Word 6.0x Windows Help Authoring Template

    HAG.ZIP
    MS Help Writer’s Authoring Guide

    Tons of other files exist, but these are the ones that I believe to be most useful and informative. Log on and browse around...you never know what you might find.

    +$#KMore WinHelp Unsolved Mysteri
    es
    ...

    Recently, I was poking around in     W
    inHelp. I performed a sort of ASCII dump of the WINHELP.EXE and HCP.EXE programs (using a program from the now defunct Compute! magazine called SuperSnooper), and discovered some undocumented commands/variables/INI file settings (we’ll just refer to this as “stuff”).

    Perhaps one of you out there know the answer to one or more of the mysterious “stuff” discovered in the ASCII dump of WINHELP.EXE and HCP.EXE. If you do, please use the WinHelp Report FormReport to let me know what you find out. To date, I have had no luck figuring these out.

    While I’m at it, let me sweeten the pot a bit: If you are the first person to unlock one of these deep, dark WinHelp secrets, I’ll give you a free registered copy of the latest version of MiniHelp PlusMHP321 AND my WinHelp 3.1 extension DLL, EW256BMP.DLLEW256BMP ... a $50 value.

    default    topic This is found in the     dump of WINHELP.EXE but not HCP.EXE. Could this, perhaps, be another possible entry for WIN.INI or WINHELP.INI? Or is this just another internal WinHelp variable? If so, are we able to access it and pass it values?

    IgnoreVersion This, too, is found     only in the dump of WINHELP.EXE. I am fairly certain that this is a possible WIN.INI entry, but cannot figure out how to implement it. Let me know what you find out. Perhaps I'm reaching, but if this works, does this mean we might be able to view Viewer .mvb files in Windows Help? Or does this exist to allow us to view Help files intended for prior versions of Windows?

    DisplayTopicOnce Like the two prev    ious discoveries, I found this only in the dump of WINHELP.EXE. This, too, I suspect, may be another possible WIN.INI entry but I do not know what value to pass to it. Is the value strictly numerical, textual, or both? Or is the value a hash value of a particular topic context string? If so, does that mean we can have several such entries in WIN.INI, one for each topic of a different Help file we want to only display once? Or is this an INI entry that belongs in WINHELP.INI?

    ShellAbout I do know what this is,     but am unsure of how to implement it. Found in WINHELP.EXE, this displays the About dialog box that is common between virtually all the native apps that ship with Windows. If you can figure out what parameters are passed to this function in SHELL.DLL, you could use the framework of the About dialog box to display your custom text.

    CbtComm Supposedly, CBT (Computer     Based Training) support has been removed from WinHelp v3.1. So, what is this? Is this a leftover from previous versions of WinHelp? Is this a CBT hook built in to WinHelp v3.1? If so, where is it implemented—in WIN.INI or WINHELP.INI? What values are passed to it?

    LowMemory Found in the ASCII dump     of WINHELP.EXE, this entry appears listed between CbtComm and SeqTopicKeys, the latter of which we discussed in the WriteProfileString topic of WINAPI2.HLP. What is its function? Where is it implemented? What does it do? What values are passed to it?

    Club %d %d gives %d keyphrases for     an estimated savings of %ld bytes. This one, found in HCP.EXE, really isn't a setting but a display message. I've never seen it displayed. When is it displayed?

    Certainly, as you can see, several things in WinHelp remain undocumented mysteries. My not being able to come up with answers for this “stuff” hasn't been for a lack of trying. I do tend to get around and ask lots of questions. Now I'm asking you to educate the rest of us if you know anything about these things I've listed above. Then all of us will be on equal footing and Microsoft’s lack of documentation won’t matter any more since we will have done it ourselves.

    +$#KOrder Form

    Name:_____________
    ________________
    ______________________________________

    Address:_________________________    _______________________________________

    City/State/ZIP:__________________    _______________________________________

    Where Did You Obtain Your Copy o    f WINAPI2.HLP?__________________________

    _________________________________    _______________________________________
    If from America Online, please sta    t
    e your screen name. Otherwise, please state your E-Mail address.

    Suggestions/Comments:___________    ___
    _____________________________________

    _________________________________    _______________________________________

    Thank you for ordering your copy o    f MiniHelp Plus v3.21. Please send $30 cash/check/money order to:

    Paul Arnote
    1208 Randolph
    Leavenworth, KS 66048

    For an a    dditional $20, you can order your copy of EW256BMP.DLL, a WinHelp add-in DLL that allows you to display 256 color bitmaps in your Help files, as well as extract files stored in WinHelp’s baggage file systems and delete those files when you are finished with them. Please mark which item(s) you are ordering. Please allow 2-4 weeks for shipping.

    _____ Order MiniHelp Plus v3.21MHP321 ONLY

    S    end $30 cash/check/money order.
     MiniHelp Plus v3.21 will be sent to you on 3½" DSHD disks, unless
    otherwise noted.

    _____ Order EW256BMP.DLL ONLY

    Send $20 cash/check/money order.
     EW256BMP.DLL will be sent to you on a 3½" DSHD disk, unless otherwise
    noted.

    _____ Order BOTH EW256BMP.DLL and MiniHelp Plus v3.21MHP321

    Send $5    0 cash/check/money order.
    All programs will be sent to you on 3½" DSHD disks, unless otherwise
    noted.

    +$#KWinHelp Report Form

    Name:_____
    _________________________
    _____________________________________

    Address:__________________________    ______________________________________

    City/State/ZIP:___________________    ______________________________________

    Internet E-Mail Address:__________    ______________________________________

    Include below a brief description o    f how this new custom macro works (i.e., the RegisterRoutine declaration, the macro format and parameters, and other pertinent data) so I can test your new discovery. Please attatch print outs of your HPJ file, the complete topic text of the topic where the macro is executed, and any other supporting files.

    ________________________________________________________________________

    __________________________________    ______________________________________

    __________________________________    ______________________________________

    __________________________________    ______________________________________

    __________________________________    ______________________________________

    __________________________________    ______________________________________

    __________________________________    ______________________________________

    __________________________________    ______________________________________

    __________________________________    ______________________________________

    __________________________________    ______________________________________

    Thank you for reporting your newly     discovered API function call that can be used by WinHelp as a custom macro. Please mail this reporting form to one of the addresses below. If you are sending your report via E-Mail, place the contents of this form in the message as text.

    Paul Arnote America Online: AerosolMan
    1208 Randolph Internet: aerosolma    n@aol.com
    Leavenworth, KS 66048 MiniHelp Pl    us Tech Support BBS: (913) 592-4163

    If you are the first to report a ne    wly discovered API function call that can be registered for use by WinHelp as a custom macro, I will include it in the next update, More WinAPI Function Calls For WinHelp: Part IV, along with your name and E-Mail address.

    +$#KMiniHelp Plus v3.21
    The Complete
    Help Writer’s Authoring Utility

    MiniHelp Plus v3.21 is the Complete
    Help Writer’s Authoring Utility that makes creating Windows Help files as easy...if not easier than...learning to use a word processor. Requires no high dollar word processor. Uses what every Windows user already has to create a full-featured authoring environment. In fact, this entire Help file was created using MiniHelp Plus v3.21.

    MiniHelp Plus v3.21 is the OFFICIAL Help Authoring Utility used by Microsoft to create the Microsoft WinShare/WinFun CompuServe Forum Newsletter Help file, currently published quarterly and available on CompuServe in the Microsoft WinShare/WinFun CompuServe Forum.

    MiniHelp Plus v3.21 allows you to create your Help file by way of a specially scripted ASCII text file in your favorite ASCII text editor. The scripting commands are easy to use and easy to remember. This is a full featured Help authoring utility. Nothing is disabled, like in some shareware Help authoring utilities.

    Here is a list of the features of MiniHelp Plus v3.21:

    • Easy to use “point-n-click” user interface.

    • Fully configurable...allows the user to define the default Help Compiler, ASCII Text Editor, Graphics Editor, and Hot Spot Editor to use in Help file construction.

    • One click Build & Test option...allows the user to convert the ASCII text file to RTF, run the Help Compiler, view the Help Compiler error file, and view the Help file—all with one click of the mouse.

    • Automatic generation of Help Project file and context-sensitive Help header file.

    • View/edit your Help file’s graphic files by selecting the View Graphics button on the user interface.

    • Stat    us bar echoes your last action.

    • Always On Top function.

    • Sports the popular 3D look.

    • And much, much more...

    This popular shareware package is available for $30 registration. When you register, you get a special MiniHelp Plus utility disk that contains Help files detailing the documented and undocumented Help macros (and then some!), a special Help file providing hints & tips on using MiniHelp Plus, a special set of utilities that allow you to peer into any Help file’s internal file system, special add-in WinHelp DLLs, and more.

    {bml bm31.SHG}Click on the graphic at left to display the order form for MiniHelp Plus. From there, you can print out the order form by clicking on the Print button on the button bar.

    +$#KEW256BMP.DLL v2.0:
    Custom WinHelp
    Extension DLL

    EW256    B    MP.DLL is a custom WinHelp Exte
    nsion DLL that allows you to display 256 color and 24-bit bitmaps in Windows Help, as well as adding 16 powerful, custom macros to the Help author’s arsenal. Here is a list of the new macros it gives you:

    • Allows you to easily store and retr    ieve files as baggage in the Help file’s internal file system.

    • Allows you to easily delete any file retrieved from the baggage file system.

    • Allows you to easily display any of 4 different styles of simple message boxes.

    • Allows you to easily display a Yes/No message box and execute other macros depending on your user’s selection.

    • Allows you to easily read and write INI file values.

    • Allows you to easily disable the Close menu item on the system menu of any Help window.

    • Allows you to easily beep the user’s speaker/sound card to get their attention.

    • Allows you to easily display a completely customizable dialog box...you define what text you want to display. Great for creating your own About dialog box.

    • Allows you to print out all or multiple topics in your Help file.

    Registration is inexpensive (only $20 U.S.), allows you free, unlimited upgrades (registration code will be valid for all future releases), and removes the NAG message box that appears when starting Help files that use the library.

    {bml bm31.SHG}Click on the graphic at left to display the order form for EW256BMP.DLL. From there, you can print out the order form by clicking on the Print button on the button bar.

    $#How To NavigateIn WinAPI 3

    When the “T    able Of Contents” Window has the focus: Select the Exit button or press X on the keyboard to exit the Help file. Select the Main Window button or press M on the keyboard to switch to the main topic window. Select the Navigation button or press N on the keyboard to display this message.

    When the MAIN topic window has the focus: Select the Contents button or press C on the keyboard to change back to the “Table Of Contents” window. Select the WinAPI2 button or press W on the keyboard to view the Help file WINAPI2.HLP, the precursor to this file. Select the Exit button or press X on the keyboard to exit this Help file.

    +$#KAbout WinAPI 3: More WinAPI Calls as C
    ustom Help Macros

    This series of “Help Help files” actuall    y
    began when I created the MiniHelp PlusMHP321 Reference Library, an extensive “how to create Help files” collection detailing the documented and undocumented Help macros, plus a MiniHelp Plus Hints & Tips Help file and several other helpful Help authoring files. Registered users ofMiniHelp PlusMHP321 receive the collection free of charge for registering their copy.

    As time went on, after the initial files were completed, I kept uncovering additional custom macros that any Help author could easily access. While researching and exploring custom macros for EW256BMP.DLLEW256BMP version 2.0, I took the time to create a Help file to explain much of what I was discovering – on my own, mind you – that I thought would be helpful to Help authors everywhere. These discoveries formed the foundation for WINAPI2.HLP.

    After WINAPI2.HLP was released, a few eager and enterprising Help authors wrote me detailing other custom macros. Thing was, the custom macros they were telling me about were already in the MiniHelp PlusMHP321 Reference Library. After writing back to these individuals telling them that I had already revealed that information in the aforementioned package, I was taken to task for “withholding this information just to sell copies of MiniHelp Plus v3.21.” That really hit below the belt, since that was never my intention...it was just that my mind was focused on the registered users of my Help authoring utility and I simply hadn’t wanted to repeat information I had already provided them.

    So, I agreed to start work on WINAPI3.HLP, which you see here, and include that information I had included previously in the MiniHelp PlusMHP321 Reference Library. I dug through some more databases, through the Windows API further, and “cruised the ’net” until I found what I thought was a worthy amount of useful material.

    Like I stated earlier in this Help file, my goal is to make this like a little “newletter” published on an “as necessary” basis (also interpret as “when enough information has been gathered to necessitate another issue” publishing schedule). With this newsletter in WinHelp format, we can create a “clearing-house” of WinHelp information, demonstrating new techniques and custom macros that are available to every WinHelp author.

    BUT, in order to do so, I need to hear from you as well. The next “issue” of WinAPI will arrive much sooner if those of you who know other WinHelp “special effects” would share them with the rest of us. So, if you have any special tips, tricks, or discoveries about Windows Help, please fill out and mail the WinHelp Report FormReport to the address on the form and it will be included in the next “issue” of WinAPI with credit given where it is due.

    $#K
    Th    is Help file was created with

    MiniHelp
    Plus v3.21

    Written by Paul A    rn    ote
    Copyright © 199    4. All Rights Reserved.

    Derived from the original MiniHelp that     appeared
    in the January, 1994 issue of Compute Mag    azine.

    Written by Tom Campbell
    Copyright © 19    94. All Rights Reserved.

    If you would like to use MiniHelp Plus v    3.21 to create
    your Help files, you may obtain your SHAREWARE copy
    by either downloading it off of America Online or a
    local bulletin board (depending if it has made it there yet).

    Or you may write to:

    Paul Arnote
    1208 Randolph
    Leavenworth, Kansas 66048

    Send $30.00 to the address above.
    Be sure to specify disk size,
    density, and program name, as well as your
    name & address. Cash, check or money order. Please make checks/money orders out to Paul Arnote. Registered users
    will receive     a free Help utility disk, filled with bitmaps, hot spot graphics, and special Help DLLs to enhance and add functionality to your Help file creations.
    While this version is not crippled in any way, registration
    removes the nag screen that appears when you first start MiniHelp Plus v3.21. Please register
    your copy if you find it useful and support the shareware concept,
    as well as helping to keep me interested enough
    to keep improving on this program.

    Back To What You Were Doing! Back()


Accueil

Accueil CHM

Normal, dit comme ça,
vous ne risquez pas de comprendre.


Introduction

Créé avec HelpNDoc Personal Edition: Environnement de création d'aide complet

The CHM file was converted to HTM by Trial version of ChmDecompiler.
Download ChmDecompiler at: http://www.zipghost.com


Tutoriel programmation

  • Forums
  • Tutoriels
  • Magazine
  • FAQs
  • Blogs
  • Projets
  • Chat
  • Newsletter
  • Études
  • Emploi
  • Club
  • Contacts
  • Accueil Actualités IT Pro
  • ALM Cycle de vie du logiciel
    • ALM
    • Merise
    • UML
  • Java Plateforme et langage Java
    • Java
    • Java Web
    • Spring
    • Android
    • Eclipse
    • NetBeans
  • .NET Microsoft Framework .NET
    • Microsoft DotNET
    • Visual Studio
    • ASP.NET
    • C#
    • VB.NET
    • Windows Phone
    • Windows Azure
  • Dév. Web Développement Web et Webmarketing
    • Développement Web
    • AJAX
    • Apache
    • ASP
    • CSS
    • Flash / Flex
    • JavaScript
    • PHP
    • Ruby & Rails
    • Web sémantique
    • Webmarketing
    • (X)HTML
  • EDI Environnements de Développement Intégré
    • EDI
    • 4D
    • Delphi
    • Eclipse
    • LabVIEW
    • NetBeans
    • MATLAB
    • Scilab
    • Visual Studio
    • WinDev
    • Visual Basic 6
    • Lazarus
    • Qt Creator
  • Programmation Programmation et langages de programmation applicatifs
    • Programmation
    • Débuter - Algorithmique
    • 2D - 3D - Jeux
    • Assembleur
    • C
    • C++
    • Objective C
    • Pascal
    • Perl
    • Python
    • Qt
    • XML
    • Autres
  • SGBD Systèmes de Gestion de Bases de Données
    • SGBD & SQL
    • 4D
    • Access
    • DB2
    • Firebird
    • InterBase
    • MySQL
    • NoSQL
    • PostgreSQL
    • Oracle
    • Sybase
    • SQL-Server
  • Office Bureautique pour l'entreprise
    • Microsoft Office
    • Access
    • Excel
    • Word
    • Outlook
    • PowerPoint
    • SharePoint
    • Microsoft Project
  • Solutions d'entreprise Autres logiciels pour l'entreprise
    • Solutions d'entreprise
    • Business Intelligence
    • ERP / PGI
    • CRM
    • SAS
    • Cloud Computing
    • SAP
    • Microsoft BizTalk Server
    • Talend
  • Applications Applications logicielles
    • Applications
    • Libres & Open Source
    • OpenOffice & LibreOffice
    • Projets
  • Mobiles Logiciels et matériels mobiles
    • Mobiles
    • Android
    • iOS
    • Windows Phone
  • Systèmes Logiciels et matériels systèmes
    • Systèmes
    • Windows
    • Linux Professionnel
    • Sécurité
    • PC
    • Mac
    • Réseau
    • Green IT
    • Virtualisation
    • Systèmes Embarqués
  • TUTORIELS
  • FAQs
  • LIVRES
  • TELECHARGEMENTS
  • SOURCES
  • DEBATS
  • WIKI
  • DICO
  • CALENDRIER
  • HUMOUR

Bienvenue sur l'espace de CGi.

Tous les articles de l'auteur :

C++ Builder

 Trucs et astuces pour C++ Builder.
Création d'un composant.
TRichEdit.
Construction d'une application MDI.
TThread.
Installation de wxWidgets avec C++ Builder.
Application au style Windows XP.

C/C++

 Les pointeurs du C et du C++.
Les listes chainées en C.
Liste simplement chaînée.
Liste triée.
Liste doublement chaînée.
 Les Arbres. New
Le C orienté objets ?

DotNet

 Création d'un composant .Net avec C# Builder.
Création d'un contrôle WinForm pour .Net.
Création d'une application MDI avec C# Builder.

wxWidgets

1 - Ma première application avec wxWidgets.
2 - Construire un menu avec wxWidgets.
3 - Dessiner avec wxWidgets.
4 - Sizers, Timers... sous wxWidgets.
5 - Les boîtes de dialogues standards.
6 - Les boîtes de dialogues personnalisées.

API Windows

 1 - La fenêtre principale.
2 - Contrôles et messages.
3 - Les commandes et notifications.
4 - Les boîtes de dialogue communes.
5 - Les contextes de périphériques.
6 - Dessiner.
7 - Les ressources.
8 - Les boîtes de dialogue personnalisées.
9 - Communiquer avec les boîtes de dialogue.
10 - Les accélérateurs clavier.

... plus.

Java

 Applet: Animation dans une applet java.

Responsable bénévole de la rubrique Accueil : le Rédacteur en Chef -

Developpez.com

Nous contacter

Participez

Informations légales

Services

Forum d'entraide

Blogs

Hébergement

Partenaires

PlanetHoster
Ikoula

Copyright © 2000-2014 - www.developpez.com

The CHM file was converted to HTM by Trial version of ChmDecompiler.
Download ChmDecompiler at: http://www.zipghost.com